Upload
mikhail-kurnosov
View
468
Download
7
Embed Size (px)
Citation preview
Лекция 6:
Многопоточное программирование
Часть 2
(Multithreading programming)
Курносов Михаил Георгиевич
к.т.н. доцент Кафедры вычислительных систем
Сибирский государственный университет
телекоммуникаций и информатики
http://www.mkurnosov.net
Программный инструментарий
22Hardware (Multi-core processors, SMP/NUMA)
Kernel thread
Process/thread scheduler
Системные вызовы (System calls)
GNU/Linux Pthreads Apple OS X Cocoa, Pthreads
Уровень ядра(Kernel space)
Операционная система (Operating System)GNU/Linux, Microsoft Windows, Apple OS X, IBM AIX, Oracle Solaris, …
Kernel thread
Kernel thread
…
Уровень пользователя
(User space)
Системные библиотеки (System libraries)
Thread Thread Thread Thread…
Win32 API/.NET Threads
Thread Thread Thread Thread
Kernel thread
Kernel thread
Kernel thread
Kernel thread
Kernel thread
Intel Threading Building Blocks (TBB) Microsoft Concurrency Runtime Apple Grand Central Dispatch Boost Threads Qthread, MassiveThreads
Прикладные библиотеки Языки программирования
OpenMP(C/C++/Fortran)
Intel Cilk Plus С++11 Threads C11 Threads
C# Threads Java Threads Erlang Threads Haskell Threads
Программный инструментарий
33Hardware (Multi-core processors, SMP/NUMA)
Kernel thread
Process/thread scheduler
Системные вызовы (System calls)
GNU/Linux Pthreads Apple OS X Cocoa, Pthreads
Уровень ядра(Kernel space)
Операционная система (Operating System)GNU/Linux, Microsoft Windows, Apple OS X, IBM AIX, Oracle Solaris, …
Kernel thread
Kernel thread
…
Уровень пользователя
(User space)
Системные библиотеки (System libraries)
Thread Thread Thread Thread…
Win32 API/.NET Threads
Thread Thread Thread Thread
Kernel thread
Kernel thread
Kernel thread
Kernel thread
Kernel thread
Intel Threading Building Blocks (TBB) Microsoft Concurrency Runtime Apple Grand Central Dispatch Boost Threads Qthread, MassiveThreads
Прикладные библиотеки Языки программирования
OpenMP(C/C++/Fortran)
Intel Cilk Plus С++11 Threads C11 Threads
C# Threads Java Threads Erlang Threads Haskell Threads
Коэффициент ускорения (Speedup)
44
Введем обозначения:
o 𝑻(𝒏) – это время выполнения последовательной программы
(sequential program)
o 𝑻𝒑(𝒏) – это время выполнения параллельной программы
(parallel program) на p процессорах
Коэффициент S ускорения параллельной программ (Speedup):
𝑺𝒑 𝒏 =𝑻(𝒏)
𝑻𝒑(𝒏)
Коэффициент ускорения 𝑆𝑝 𝑛 показывает во сколько раз
параллельная программа выполняется на p процессорах быстрее
последовательной программы при обработке одних и тех же входных
данных размера n
Как правило 𝑆𝑝 𝑛 ≤ 𝑝
Коэффициент ускорения (Speedup)
55
Введем обозначения:
o 𝑻(𝒏) – это время выполнения последовательной программы
(sequential program)
o 𝑻𝒑(𝒏) – это время выполнения параллельной программы
(parallel program) на p процессорах
Коэффициент S ускорения параллельной программ (Speedup):
𝑺𝒑 𝒏 =𝑻(𝒏)
𝑻𝒑(𝒏)
Цель распараллеливания – достичь линейного ускорения
на максимально большом числе процессоров
𝑺𝒑 𝒏 ≈ 𝒑 или 𝑺𝒑 𝒏 = 𝑶(𝒑) при 𝒑 → ∞
Коэффициент ускорения (Speedup)
66
Какое время брать за 𝑻(𝒏) – за время выполнения
последовательной программы?
o Время лучшего известного алгоритма (в смысле вычислительной
сложности)?
o Время лучшего теоретически возможного алгоритма?
Что считать временем выполнения 𝑻𝒑 𝒏 параллельной
программы?
o Среднее время выполнения потоков программы?
o Время выполнения потока, завершившего работу первым?
o Время выполнения потока, завершившего работу последним?
Коэффициент ускорения (Speedup)
77
Какое время брать за 𝑻(𝒏) – за время выполнения
последовательной программы?
o Время лучшего известного алгоритма или время алгоритма,
который подвергается распараллеливанию
Что считать временем выполнения 𝑻𝒑 𝒏 параллельной
программы?
o Время выполнения потока, завершившего работу последним
Коэффициент ускорения (Speedup)
88
Коэффициент относительного ускорения (Relative speedup) –
отношения времени выполнения параллельной программы на одном
процессоре к времени её выполнения на p процессорах
𝑺𝑹𝒆𝒍𝒂𝒕𝒊𝒗𝒆 𝒑, 𝒏 =𝑻𝟏(𝒏)
𝑻𝒑(𝒏)
Коэффициент эффективности (Efficiency) параллельной программы
𝑬𝒑 𝒏 =𝑺𝒑(𝒏)
𝒏=𝑻(𝒏)
𝒏𝑻𝒑(𝒏)∈ [𝟎, 𝟏]
Коэффициент накладных расходов (Overhead)
𝜺 𝒑, 𝒏 =𝑻𝑺𝒚𝒏𝒄(𝒑, 𝒏)
𝑻𝑪𝒐𝒎𝒑(𝒑, 𝒏)=𝑻𝑻𝒐𝒕𝒂𝒍 𝒑, 𝒏 − 𝑻𝑪𝒐𝒎𝒑(𝒑, 𝒏)
𝑻𝑪𝒐𝒎𝒑(𝒑, 𝒏)
𝑻𝑺𝒚𝒏𝒄(𝒑, 𝒏) – время создания, синхронизации и взаимодействия p потоков
𝑻𝑪𝒐𝒎𝒑(𝒑, 𝒏) – время вычислений в каждом из p потоков
Коэффициент ускорения (Speedup)
99
0
5
10
15
20
25
30
35
2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32
S
p
Spee
du
p
Processors
Linear (ideal)
N = 60 * 2^20
N = 40 * 2^20
N = 20 * 2^20
Ускорение программы может расти с увеличением размера входных данных –
время вычислений превосходит накладные расходы на взаимодействия
потоков (управление потоками, синхронизацию, обмен сообщениями, …)
Зависимость коэффициента ускорения Sпараллельного алгоритма X от количества p процессоров
Коэффициент ускорения (Speedup)
1010
0
5
10
15
20
25
30
35
2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32
S
p
Spee
du
p
Processors
Linear (ideal)
N = 60 * 2^20
N = 40 * 2^20
N = 20 * 2^20
Ускорение программы может расти с увеличением размера входных данных –
время вычислений превосходит накладные расходы на взаимодействия
потоков (управление потоками, синхронизацию, обмен сообщениями, …)
Зависимость коэффициента ускорения Sпараллельного алгоритма X от количества p процессоров
Линейное ускорение
Sp(n) = 4/5 * p = O(p)
Линейное ускорение
Sp(n) = 1/5 * p = O(p)
Коэффициент ускорения (Speedup)
1111
Параллельная программа (алгоритм) коэффициент
ускорения, которой линейной растет с увеличением p
называется линейно масштабируемой или просто
масштабируемой (scalable)
Масштабируемая параллельная программа допускает
эффективную реализацию на различном числе
процессоров
0
5
10
15
20
25
30
35
2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32
Коэффициент ускорения (Speedup)
1212
S
p
Spee
du
p
Processors
Linear (ideal)
Зависимость коэффициента ускорения Sпараллельных алгоритмов Y и Z от количества p процессоров
Логарифмическое ускорение
Sp(n) = log2p = O(log2p)
𝑺𝒑 𝒏 = 𝒑 = 𝑶( 𝒑)
Суперлинейное ускорение (Superlinear speedup)
1313
Параллельная программа может характеризоваться суперлинейным
ускорением (Superlinear speedup) – коэффициент ускорения Sp(n) принимает
значение больше p
𝑺𝒑 𝒏 > 𝒑
Причина: иерархическая организация памяти:
Cache – RAM – Local disk (HDD/SSD) – Network storage
Последовательная программ выполняется на одном процессоре
и обрабатывает данные размера n
Параллельная программа имеет p потоков на p процессорах, каждый поток
работает со своей частью данных, большая часть которых может попасть
в кеш-память, в результате в каждом потоке сокращается время доступа
к данным
Тот же самый эффект можно наблюдать имя два уровня иерархической
памяти: диск-память
Суперлинейное ускорение (Superlinear speedup)
1414http://phycomp.technion.ac.il/~pavelba/Comp_Phys/Project/Project.html
Parallel Molecular Dynamic Simulation MPI, Spatial decomposition;
Cluster nodes: 2 x AMD Opteron Dual Core; InfiniBand network
Superlinear speedup
𝑺𝟖 =𝑻𝟒𝑻𝟖= 𝟐. 𝟑𝟐
Закон Дж. Амдала (Amdahl’s law)
1515
Пусть имеется последовательная программа c временем выполнения T(n)
Обозначим:
𝒓 ∈ [𝟎, 𝟏] – часть программы, которая может быть распараллелена
(perfectly parallelized)
𝒔 = 𝟏 − 𝒓 – часть программы, которая не может быть распараллелена
(purely sequential)
Закон Дж. Амдала (Gene Amdahl) [1]:
Максимальное ускорение Sp программы на p процессорах равняется
𝑺𝒑 =𝟏
𝟏 − 𝒓 +𝒓𝒑
𝑺∞ = lim𝑝→∞𝑆𝑝 = lim
𝑝→∞
1
1 − 𝑟 +𝑟𝑝
=1
1 − 𝑟=𝟏
𝒔
Amdahl Gene. Validity of the Single Processor Approach to Achieving Large-Scale Computing Capabilities //
AFIPS Conference Proceedings, 1967, pp. 483-485, http://www-inst.eecs.berkeley.edu/~n252/paper/Amdahl.pdf
s
r
Обоснование закона Дж. Амдала
1616
Время выполнения последовательной программы есть 𝑇(𝑛)
Время выполнения параллельной программы на p процессорах
(время каждого потока) складывается из последовательной части s
и параллельной r:
𝑇𝑝 𝑛 = 𝑇 𝑛 𝑠 +𝑇(𝑛)
𝑝𝑟
Вычислим значение коэффициент ускорения (по определению)
𝑆𝑝 𝑛 =𝑇(𝑛)
𝑇𝑝 𝑛=
𝑇(𝑛)
𝑇 𝑛 𝑠 +𝑇(𝑛)𝑝𝑟=1
𝑠 +𝑟𝑝
=1
(1 − 𝑟) +𝑟𝑝
s
r
0
5
10
15
20
25
0,1 0,2 0,3 0,4 0,5 0,6 0,7 0,8 0,9 0,95
Закон Дж. Амдала (Amdahl’s law)
1717
r
Зависимость коэффициента S∞ ускорения параллельной программы от доли r распараллеленного кода
S∞
Закон Дж. Амдала (Amdahl’s law)
1818
Допущения закона Амдала
Последовательный алгоритм является наиболее оптимальным способом
решения задачи. Возможны ситуации когда параллельная программа
(алгоритм) эффективнее решает задачу (может эффективнее использовать
кеш-память, конвейер, SIMD-инструкции, …)
Время выполнения параллельной программы оценивается через время
выполнения последовательной, однако потоки параллельной программы
могут выполнятся эффективнее
𝑇𝑝 𝑛 = 𝑇 𝑛 𝑠 +𝑇(𝑛)
𝑝𝑟
Ускорение Sp(n) оценивается для фиксированного размера n данных при
любых значениях p. На практике при увеличении числа используемых
процессоров размер n входных данных также увеличивают, так как может
быть доступно больше памяти
Закон Дж. Амдала (Amdahl’s law)
1919
Следствие. Увеличение параллельной части r программ важнее,
чем увеличение количества процессоров
0
10
20
30
40
50
60
70
80
90
2 4 8 16 32
r = 30%
r = 60%
r = 90%
p
Зависимость времени Tp(n) выполнения параллельной программыот количества p процессоров и доли r распараллеленного кода
(время в % от времени T1(n))
T, %
Закон Дж. Амдала (Amdahl’s law)
2020
Следствие. Увеличение параллельной части r программ важнее,
чем увеличение количества процессоров
0
10
20
30
40
50
60
70
80
90
2 4 8 16 32
r = 30%
r = 60%
r = 90%
p
Зависимость времени Tp(n) выполнения параллельной программыот количества p процессоров и доли r распараллеленного кода
(время в % от времени T1(n))
T, %Удвоение числа процессоров
сокращает время с 85% до 77,5%
Удвоение доли параллельного кода сокращает время
с 85% до 70%
Закон Густафсона-Барсиса
2121
В 1980-х годах показали, что некоторые практические приложения могут
масштабироваться лучше (почти линейно), чем предсказывает закон Амдала!
Пусть имеется последовательная программа c временем выполнения T(n)
Обозначим 𝒔 ∈ [𝟎, 𝟏] – часть программы, которая не может быть распараллелена
(purely sequential)
Закон Густафсона-Барсиса (Gustafson–Barsis' law) [1]:
Масштабируемое ускорение Sp программы на p процессорах равняется
𝑺𝒑 = 𝒑 − 𝒔(𝒑 − 𝟏)
Обоснование
Пусть a – время последовательной части, b – время параллельной части
𝑻𝒑 𝒏 = 𝒂 + 𝒃, 𝑻 𝒏 = 𝒂 + 𝒑𝒃
𝐬 = 𝒂/(𝒂 + 𝒃), 𝑺𝒑 𝒏 = 𝒔 + 𝒑 𝟏 − 𝒔 = 𝒑 − 𝒔(𝒑 − 𝟏)
Время выполнения последовательной программы выражается через время выполнения
параллельной
Reevaluating Amdahl's Law, John L. Gustafson, Communications of the ACM 31(5), 1988. pp. 532-533 //
http://www.scl.ameslab.gov/Publications/Gus/AmdahlsLaw/Amdahls.html
POSIX Threads
2222
POSIX Threads – это стандарт (POSIX.1c Threads extensions
(IEEE Std 1003.1c-1995)), в котором определяется API
для создания и управления потоками
Библиотека pthread (pthread.h) ~ 100 функций
Thread management - creating, joining threads etc.
Mutexes
Condition variables
Synchronization between threads using read/write locks and
barriers
Семфафоры POSIX (префикс sem_) могут работать
с потоками pthread, но не являются частью стандарта
(определены в стандарте POSIX.1b, Real-time extensions
(IEEE Std 1003.1b-1993))
POSIX pthreads API
2323
Всем типы данных и функции начинаются с префикса pthread_
Prefix Functional group
pthread_Threads themselves and miscellaneous subroutines
pthread_attr_ Thread attributes objects
pthread_mutex_ Mutexes
pthread_mutexattr_ Mutex attributes objects.
pthread_cond_ Condition variables
pthread_condattr_ Condition attributes objects
pthread_key_ Thread-specific data keys
pthread_rwlock_ Read/write locks
pthread_barrier_ Synchronization barriers
https://computing.llnl.gov/tutorials/pthreads
POSIX pthreads API
2424
Compiler / Platform Compiler Command Description
Intel GNU/Linux
icc -pthread C
icpc -pthread C++
PGIGNU/Linux
pgcc -lpthread C
pgCC -lpthread C++
GNU GCCGNU/Linux, Blue Gene
gcc -pthread GNU C
g++ -pthread GNU C++
IBMBlue Gene
bgxlc_r / bgcc_r C (ANSI / non-ANSI)
bgxlC_r, bgxlc++_r C++
Компиляция программы с поддержкой POSIX pthreads API
Создание потоков
2525
int pthread_create(pthread_t *restrict thread, const pthread_attr_t *restrict attr, void *(*start_routine)(void*), void *restrict arg);
Создает поток с заданными атрибутами attr и запускает в нем
функцию start_routine, передавая ей аргумент arg
Количество создаваемых в процессе потоков стандартом
не ограничивается и зависит от реализации
Размер стека потока можно задать через атрибут потока attr
Размер стека по умолчанию: getrlimit(RLIMIT_STRACK, &rlim);
$ ulimit –s # The maximum stack size
8192
$ ulimit –u # The maximum number of processes
1024 # available to a single usere
Завершение потоков
2626
Возврат (return) из стартовой функции (start_routine)
Вызов pthread_exit()
Вызов pthread_cancel() другим потоком
Процесс (и его потоки) завершаются вызовом exit()
#include <pthread.h> #define NTHREADS 5
void *thread_fun(void *threadid) { long tid = (long)threadid; printf("Hello from %ld!\n", tid); pthread_exit(NULL);
}
int main(int argc, char *argv[]) { pthread_t threads[NTHREADS]; int rc; long t;
for (t = 0; t < NTHREADS; t++) { rc = pthread_create(&threads[t], NULL, thread_fun, (void *)t); if (rc) {
printf("ERROR %d\n", rc); exit(-1);
} } pthread_exit(NULL);
}
Создание и завершение потоков
2727
#include <pthread.h> #define NTHREADS 5
void *thread_fun(void *threadid) { long tid = (long)threadid; printf("Hello from %ld!\n", tid); pthread_exit(NULL);
}
int main(int argc, char *argv[]) { pthread_t threads[NTHREADS]; int rc; long t;
for (t = 0; t < NTHREADS; t++) { rc = pthread_create(&threads[t], NULL, thread_fun, (void *)t); if (rc) {
printf("ERROR %d\n", rc); exit(-1);
} } pthread_exit(NULL);
}
Создание и завершение потоков
2828
$ gcc –pthread –o prog ./prog.c
$ ./prog
Hello from 1!
Hello from 4!
Hello from 0!
Hello from 2!
Hello from 3!
Ожидание потоков
2929
Функция pthread_join() – позволяет дождаться завершения заданного
потока
Поток может быть типа “detached” или “joinable” (default)
К detached-потоку не применима функция pthread_join
(поток создается и существует независимо от других)
Joinable-поток требует хранения дополнительных данных
Тип потока можно задать через его атрибуты или вызвав функцию
pthread_detach
#include <pthread.h> #define NTHREADS 5
// ...
int main(int argc, char *argv[]) { pthread_t threads[NTHREADS]; int rc; long t;void *status;
for (t = 0; t < NTHREADS; t++) { rc = pthread_create(&threads[t], NULL, thread_fun,
(void *)t); }
for (t = 0; t < NTHREADS; t++) { rc = pthread_join(threads[t], &status);
} pthread_exit(NULL);
}
Ожидание потоков
3030
Синхронизация потоков
3131
Функция pthread_self() – возвращает идентификатор потока
Функция pthread_equal() – позволяет сравнить идентификаторы двух
потоков
Взаимные исключения (mutex)
3232
Mutex (mutual exclusion) – это объект синхронизации “взаимное
исключение”
Мьютексы используются для создания критических секций
(critical sections) – областей кода, которые выполняются в любой
момент времени только одним потоком
В критических секциях, как правило, содержится код работы
с разделяемыми переменными
pthread_mutex_init() – инициализирует мьютекс
pthread_mutex_destroy() – уничтожает мьютекс
pthread_mutex_lock() – блокирует выполнение потока,
пока он не захватит (acquire) мьютекс
pthread_mutex_trylock() – осуществляет попытку захватить мьютекс
pthread_mutex_unlock() – освобождает (release) мьютекс
node_t *llist_delete(int value) {
node_t *prev, *current; prev = &head;
pthread_mutex_lock(&prev->lock); while ((current = prev->link) != NULL) {
pthread_mutex_lock(¤t->lock); if (current->value == value) {
prev->link = current->link; pthread_mutex_unlock(¤t->lock); pthread_mutex_unlock(&prev->lock); current->link = NULL; return current;
} pthread_mutex_unlock(&prev->lock); prev = current;
} pthread_mutex_unlock(&prev->lock); return NULL;
}
Взаимные исключения (mutex)
3333
0,00
0,50
1,00
1,50
2,00
2,50
3,00
3,50
4,00
4,50
0,00 0,10 0,20 0,30 0,40 0,50 0,60 0,70 0,80 0,90
Вычисление числа π
3434
𝜋 =
0
14
1 + 𝑥2𝑑𝑥 𝜋 ≈ ℎ
𝑖=1
𝑛4
1 + (ℎ(𝑖 − 0.5))2ℎ =1
𝑛
Приближенное вычисление числа π
#include <stdio.h>#include <stdlib.h>#include <pthread.h>
volatile long double pi = 0.0; pthread_mutex_t piLock; long double intervals; int numThreads;
void *computePI(void *id){
long double x, width, localSum = 0; int i, threadID = *((int*)id); width = 1.0 / intervals; for (i = threadID ; i < intervals; i += numThreads) {
x = (i + 0.5) * width; localSum += 4.0 / (1.0 + x * x);
} localSum *= width; pthread_mutex_lock(&piLock); pi += localSum; pthread_mutex_unlock(&piLock); return NULL;
}
pi.c
3535
int main(int argc, char **argv){
pthread_t *threads; void *retval; int *threadID; int i;
if (argc == 3) { intervals = atoi(argv[1]); numThreads = atoi(argv[2]); threads = malloc(numThreads * sizeof(pthread_t)); threadID = malloc(numThreads * sizeof(int)); pthread_mutex_init(&piLock, NULL); for (i = 0; i < numThreads; i++) {
threadID[i] = i; pthread_create(&threads[i], NULL, computePI, threadID + i);
} for (i = 0; i < numThreads; i++)
pthread_join(threads[i], &retval);
printf("Estimation of pi is %32.30Lf \n", pi); } else {
printf("Usage: ./a.out <numIntervals> <numThreads>\n"); } return 0;
}
pi.c (продолжение)
3636
Ссылки
3737
Эхтер Ш., Робертс Дж. Многоядерное программирование. – СПб.: Питер,
2010. – 316 с.
Эндрюс Г.Р. Основы многопоточного, параллельного и распределенного
программирования. – М.: Вильямс, 2003. – 512 с.
Darryl Gove. Multicore Application Programming: for Windows, Linux, and
Oracle Solaris. – Addison-Wesley, 2010. – 480 p.
Maurice Herlihy, Nir Shavit. The Art of Multiprocessor Programming. –
Morgan Kaufmann, 2008. – 528 p.
Richard H. Carver, Kuo-Chung Tai. Modern Multithreading : Implementing,
Testing, and Debugging Multithreaded Java and C++/Pthreads/Win32
Programs. – Wiley-Interscience, 2005. – 480 p.
Anthony Williams. C++ Concurrency in Action: Practical Multithreading. –
Manning Publications, 2012. – 528 p.
Träff J.L. Introduction to Parallel Computing //
http://www.par.tuwien.ac.at/teach/WS12/ParComp.html