SlideShare a Scribd company logo
1 of 85
Download to read offline
Курносов Михаил Георгиевич
E-mail: mkurnosov@gmail.com
WWW: www.mkurnosov.net
Курс «Высокопроизводительные вычислительные системы»
Сибирский государственный университет телекоммуникаций и информатики (Новосибирск)
Осенний семестр, 2015
Лекция 6
Стандарт OpenMP
Программный инструментарий
22Hardware (Multi-core processors, SMP/NUMA)
Kernel
thread
Process/thread scheduler
Системные вызовы (System calls)
POSIX Threads 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
 С++14 Threads
 C11 Threads
 C# Threads
 Java Threads
 Erlang Threads
 Haskell Threads
Стандарт OpenMP
33
 OpenMP (Open Multi-Processing) – стандарт, определяющий набор
директив компилятора, библиотечных процедур и переменных
среды окружения для создания многопоточных программ
 Разрабатывается в рамках OpenMP Architecture Review Board
с 1997 года
 http://www.openmp.org
 OpenMP 2.5 (2005), OpenMP 3.0 (2008), OpenMP 3.1 (2011),
OpenMP 4.0 (2013)
 Требуется поддержка со стороны компилятора
OpenMP
44
Compiler Information
GNU GCC Option: –fopenmp
gcc 4.2 – OpenMP 2.5,
gcc 4.4 – OpenMP 3.0,
gcc 4.7 – OpenMP 3.1
gcc 4.9 – OpenMP 4.0
Clang (LLVM) OpenMP 3.1
Clang + Intel OpenMP RTL
http://clang-omp.github.io/
Intel C/C++, Fortran OpenMP 4.0
Option: –Qopenmp, –openmp
Oracle Solaris Studio C/C++/Fortran OpenMP 4.0
Option: –xopenmp
Microsoft Visual Studio C++ Option: /openmp
OpenMP 2.0 only
Other compilers: IBM XL, PathScale, PGI, Absoft Pro, …
http://openmp.org/wp/openmp-compilers/
Модель выполнения OpenMP-программы
55
0 1
0
0 1 2
0 Динамическое управление потоками
в модели Fork-Join:
 Fork – порождение нового потока
 Join – ожидание завершения потока
(объединение потоков управления)
 OpenMP-программа – совокупность
последовательных участков кода
(serial code) и параллельных регионов
(parallel region)
 Каждый поток имеет логический номер:
0, 1, 2, …
 Главный поток (master) имеет номер 0
 Параллельные регионы могут быть вложенными
(nested)
Parallel
region
Parallel
region
Barrier
Barrier
Пример OpenMP-программы
66
#include <stdio.h>
#include <omp.h>
int main(int argc, char **argv)
{
#pragma omp parallel /* <-- Fork */
{
printf("Hello, multithreaded world: thread %d of %dn",
omp_get_thread_num(), omp_get_num_threads());
} /* <-- Barrier & join */
return 0;
}
Компиляция и запуск OpenMP-программы
77
 По умолчанию количество потоков в параллельном
регионе равно числу логических процессоров в системе
 Порядок выполнения потоков заранее неизвестен –
определяется планировщиком операционной системы
$ gcc –fopenmp –o hello ./hello.c
$ ./hello
Hello, multithreaded world: thread 0 of 4
Hello, multithreaded world: thread 1 of 4
Hello, multithreaded world: thread 3 of 4
Hello, multithreaded world: thread 2 of 4
Указание числа потоков
88
$ export OMP_NUM_THREADS=8
$ ./hello
Hello, multithreaded world: thread 1 of 8
Hello, multithreaded world: thread 2 of 8
Hello, multithreaded world: thread 3 of 8
Hello, multithreaded world: thread 0 of 8
Hello, multithreaded world: thread 4 of 8
Hello, multithreaded world: thread 5 of 8
Hello, multithreaded world: thread 6 of 8
Hello, multithreaded world: thread 7 of 8
Задание числа потоков
99
#include <stdio.h>
#include <omp.h>
int main(int argc, char **argv)
{
#pragma omp parallel num_threads(6)
{
printf("Hello, multithreaded world: thread %d of %dn",
omp_get_thread_num(), omp_get_num_threads());
}
return 0;
}
Указание числа потоков
1010
$ export OMP_NUM_THREADS=8
$ ./hello
Hello, multithreaded world: thread 2 of 6
Hello, multithreaded world: thread 3 of 6
Hello, multithreaded world: thread 1 of 6
Hello, multithreaded world: thread 0 of 6
Hello, multithreaded world: thread 4 of 6
Hello, multithreaded world: thread 5 of 6
 Директива num_threads имеет приоритет над значением
переменной среды окружения OMP_NUM_THREADS
Список потоков процесса
1111
#include <stdio.h>
#include <omp.h>
#include <time.h>
int main(int argc, char **argv)
{
#pragma omp parallel num_threads(6)
{
printf("Hello, multithreaded world: thread %d of %dn",
omp_get_thread_num(), omp_get_num_threads());
/* Sleep for 30 seconds */
nanosleep(&(struct timespec){.tv_sec = 30}, NULL);
}
return 0;
}
Список потоков процесса
1212
$ ./hello &
$ ps -eLo pid,tid,psr,args | grep hello
6157 6157 0 ./hello
6157 6158 1 ./hello
6157 6159 0 ./hello
6157 6160 1 ./hello
6157 6161 0 ./hello
6157 6162 1 ./hello
6165 6165 2 grep hello
 Номер процесса (PID)
 Номер потока (TID)
 Логический процессор (PSR)
 Название исполняемого файла
 Информация о логических процессорах системы:
 /proc/cpuinfo
 /sys/devices/system/cpu
Условная компиляция
1313
#include <stdio.h>
#include <omp.h>
int main(int argc, char **argv)
{
#ifdef _OPENMP
#pragma omp parallel num_threads(6)
{
printf("Hello, multithreaded world: thread %d of %dn",
omp_get_thread_num(), omp_get_num_threads());
}
printf("OpenMP version %dn", _OPENMP);
if (_OPENMP >= 201107)
printf(" OpenMP 3.1 is supportedn");
#endif
return 0;
}
Синтаксис директив OpenMP
1414
 Языки С/C++
#pragma omp directive-name [clause[ [,] clause]...] new-line
#pragma omp parallel
 Fortran
sentinel directive-name [clause[[,] clause]...]
!$omp parallel
 Создание потоков
 Распределение вычислений между потоками
 Управление пространством видимости переменных
 Синхронизация потоков
 …
Создание потоков (parallel)
1515
#pragma omp parallel
{
/* Этот код выполняется всеми потоками */
}
#pragma omp parallel if (expr)
{
/* Код выполняется потоками если expr = true */
}
#pragma omp parallel num_threads(n / 2)
{
/* Код выполняется n / 2 потоками */
}
На выходе из параллельного региона осуществляется барьерная
синхронизация – все потоки ждут последнего
Создание потоков (sections)
1616
#pragma omp parallel sections
{
#pragma omp section
{
/* Код потока 0 */
}
#pragma omp section
{
/* Код потока 1 */
}
}
При любых условиях выполняется фиксированное
количество потоков (по количеству секций)
Функции runtime-библиотеки
1717
 int omp_get_thread_num()
– возвращает номер текущего потока
 int omp_get_num_threads()
– возвращает количество потоков в параллельном регионе
 void omp_set_num_threads(int n)
 double omp_get_wtime()
Директива master
1818
#pragma omp parallel
{
/* Этот код выполняется всеми потоками */
#pragma omp master
{
/* Код выполняется только потоком 0 */
}
/* Этот код выполняется всеми потоками */
}
Директива single
1919
#pragma omp parallel
{
/* Этот код выполняется всеми потоками */
#pragma omp single
{
/* Код выполняется только одним потоком */
}
/* Этот код выполняется всеми потоками */
}
Директива for (data parallelism)
2020
#define N 13
#pragma omp parallel
{
#pragma omp for
for (i = 0; i < N; i++) {
printf("Thread %d i = %dn",
omp_get_thread_num(), i);
}
}
Итерации цикла распределяются между потоками
0 1 2 3 4 5 6 7 8 9 10 11 12i:
Thread 0 Thread 1 Thread 2 Thread 3
Директива for
2121
$ OMP_NUM_THREADS=4 ./prog
Thread 2 i = 7
Thread 2 i = 8
Thread 2 i = 9
Thread 0 i = 0
Thread 0 i = 1
Thread 0 i = 2
Thread 3 i = 10
Thread 3 i = 11
Thread 3 i = 12
Thread 0 i = 3
Thread 1 i = 4
Thread 1 i = 5
Thread 1 i = 6
Алгоритмы распределения итераций
22
#define N 13
#pragma omp parallel
{
#pragma omp for schedule(static, 2)
for (i = 0; i < N; i++) {
printf("Thread %d i = %dn",
omp_get_thread_num(), i);
}
}
0 1 2 3 4 5 6 7 8 9 10 11 12i:
Итерации цикла распределяются циклически (round-robin)
блоками по 2 итерации
T0 T1 T2 T3 T0 T1 T2
Алгоритмы распределения итераций
2323
$ OMP_NUM_THREADS=4 ./prog
Thread 0 i = 0
Thread 0 i = 1
Thread 0 i = 8
Thread 0 i = 9
Thread 1 i = 2
Thread 1 i = 3
Thread 1 i = 10
Thread 1 i = 11
Thread 3 i = 6
Thread 3 i = 7
Thread 2 i = 4
Thread 2 i = 5
Thread 2 i = 12
Алгоритм Описание
static, m
Цикл делится на блоки по m итераций
(до выполнения), которые распределяются
по потокам
dynamic, m
Цикл делится на блоки по m итераций.
При выполнении блока из m итераций поток выбирает
следующий блок из общего пула
guided, m
Блоки выделяются динамически. При каждом запросе
размер блока уменьшается экспоненциально до m
runtime
Алгоритм задается пользователем через переменную
среды OMP_SCHEDULE
Алгоритмы распределения итераций
2424
Директива for (ordered)
2525
#define N 7
#pragma omp parallel
{
#pragma omp for ordered
for (i = 0; i < N; i++) {
#pragma omp ordered
printf("Thread %d i = %dn",
omp_get_thread_num(), i);
}
}
 Директива ordered организует последовательное
выполнение итераций (i = 0, 1, …) – синхронизация
 Поток с i = k ожидает пока потоки с i = k – 1, k – 2, …
не выполнят свои итерации
Директива for (ordered)
2626
$ OMP_NUM_THREADS=4 ./prog
Thread 0 i = 0
Thread 0 i = 1
Thread 1 i = 2
Thread 1 i = 3
Thread 2 i = 4
Thread 2 i = 5
Thread 3 i = 6
Директива for (nowait)
2727
#define N 7
#pragma omp parallel
{
#pragma omp for nowait
for (i = 0; i < N; i++) {
printf("Thread %d i = %dn",
omp_get_thread_num(), i);
}
}
 По окончанию цикла потоки не выполняют
барьерную синхронизацию
 Конструкция nowait применима и к директиве sections
Директива for (collapse)
2828
#define N 3
#define M 4
#pragma omp parallel
{
#pragma omp for collapse(2)
for (i = 0; i < N; i++) {
for (j = 0; j < M; j++)
printf("Thread %d i = %dn",
omp_get_thread_num(), i);
}
}
 сollapse(n) объединяет пространство итераций n циклов в одно
0 1 2 0 1 2 3i: j:
0,0 0,1 0,2 0,3 1,0 1,1 1,2 1,3 2,0 2,1 2,2 2,3
T0
+
T1 T2 T3
Директива for (collapse)
2929
$ OMP_NUM_THREADS=4 ./prog
Thread 2 i = 1
Thread 2 i = 1
Thread 2 i = 2
Thread 0 i = 0
Thread 0 i = 0
Thread 0 i = 0
Thread 3 i = 2
Thread 3 i = 2
Thread 3 i = 2
Thread 1 i = 0
Thread 1 i = 1
Thread 1 i = 1
#include <iostream>
#include <vector>
int main()
{
std::vector<int> vec(1000);
std::fill(vec.begin(), vec.end(), 1);
int counter = 0;
#pragma omp parallel for
for (std::vector<int>::size_type i = 0;
i < vec.size(); i++)
{
if (vec[i] > 0) {
counter++;
}
}
std::cout << "Counter = " << counter << std::endl;
return 0;
}
Ошибки в многопоточных программах
30
Ошибки в многопоточных программах
3131
$ g++ –fopenmp –o ompprog ./ompprog.cpp
$ ./omprog
Counter = 381
$ ./omprog
Counter = 909
$ ./omprog
Counter = 398
На каждом запуске итоговое значение Counter разное!
Правильный результат Counter = 1000
#include <iostream>
#include <vector>
int main()
{
std::vector<int> vec(1000);
std::fill(vec.begin(), vec.end(), 1);
int counter = 0;
#pragma omp parallel for
for (std::vector<int>::size_type i = 0;
i < vec.size(); i++)
{
if (vec[i] > 0) {
counter++;
}
}
std::cout << "Counter = " << counter << std::endl;
return 0;
}
Ошибки в многопоточных программах
32
Потоки осуществляют конкурентный
доступ к переменной counter –
одновременно читают её и записывают
Thread 0 Thread 1
Memory
(counter)
0
movl [counter], %eax ← 0
incl %eax 0
movl %eax, [counter] → 1
movl [counter], %eax ← 1
incl %eax 1
movl %eax, [counter] → 2
Состояние гонки (Race condition, data race)
33
#pragma omp parallel
{
counter++;
}
movl [counter], %eax
incl %eax
movl %eax, [counter]
C++
Идеальная последовательность выполнения инструкций 2-х потоков
counter = 2
Thread 0 Thread 1
Memory
(counter)
0
movl [counter], %eax ← 0
incl %eax movl [counter], %eax ← 0
movl %eax, [counter] incl %eax → 1
movl %eax, [counter] → 1
1
Состояние гонки (Race condition, data race)
34
movl [counter], %eax
incl %eax
movl %eax, [counter]
C++
Возможная последовательность выполнения инструкций 2-х потоков
counter = 1
Error: Data race
#pragma omp parallel
{
counter++;
}
Состояние гонки (Race condition, data race)
35
 Состояние гонки (Race condition, data race) –
это состояние программы, в которой несколько потоков
одновременно конкурируют за доступ к общей структуре
данных (для чтения/записи)
 Порядок выполнения потоков заранее не известен –
носит случайный характер
 Планировщик динамически распределяет процессорное время
учитывая текущую загруженность процессорных ядер,
а нагрузку (потоки, процессы) создают пользователи,
поведение которых носит случайных характер
 Состояние гонки данных (Race condition, data race) трудно
обнаруживается в программах и воспроизводится в тестах
 Состояние гонки данных (Race condition, data race) –
это типичный пример Гейзенбага (Heisenbug)
Обнаружение состояния гонки (Data race)
36
Динамические анализаторы
 Valgrind Helgrind, DRD
 Intel Thread Checker
 Oracle Studio Thread Analyzer
 Java ThreadSanitizer
 Java Chord
Статические анализаторы кода
 PVS-Studio (viva64)
 …
==8238== Helgrind, a thread error detector
==8238== Copyright (C) 2007-2012, and GNU GPL'd, by OpenWorks LLP et al.
==8238== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==8238== Command: ./ompprog report
...
==8266== ----------------------------------------------------------------
==8266== Possible data race during write of size 4 at 0x7FEFFD358 by thread #3
==8266== Locks held: none
==8266== at 0x400E6E: main._omp_fn.0 (ompprog.cpp:14)
==8266== by 0x3F84A08389: ??? (in /usr/lib64/libgomp.so.1.0.0)
==8266== by 0x4A0A245: ??? (in /usr/lib64/valgrind/vgpreload_helgrind-amd64-linux.so)
==8266== by 0x34CFA07C52: start_thread (in /usr/lib64/libpthread-2.17.so)
==8266== by 0x34CF2F5E1C: clone (in /usr/lib64/libc-2.17.so)
==8266==
==8266== This conflicts with a previous write of size 4 by thread #1
==8266== Locks held: none
==8266== at 0x400E6E: main._omp_fn.0 (ompprog.cpp:14)
==8266== by 0x400CE8: main (ompprog.cpp:11)...
Valgrind Helgrind
37
$ g++ -fopenmp -o ompprog ./ompprog.cpp
$ valgrind --tool=helgrind ./ompprog
Директивы синхронизации
3838
 Директивы синхронизации позволяют управлять
порядком выполнения заданных участков кода потоками
 #pragma omp critical
 #pragma omp atomic
 #pragma omp ordered
 #pragma omp barrier
#pragma omp parallel for private(v)
for (i = 0; i < n; i++) {
v = fun(a[i]);
#pragma omp critical
{
sum += v;
}
}
Критические секции
3939
#pragma omp parallel for private(v)
for (i = 0; i < n; i++) {
v = fun(a[i]);
#pragma omp critical
{
sum += v;
}
}
 Критическая секция (Critical section) – участок кода
в многопоточной программе, выполняемый всеми
потоками последовательно
 Критические секции снижают степень параллелизма
Критические секции
4040
Управление видимостью переменных
4141
 private(list) – во всех потоках создаются локальные копии
переменных (начальное значение)
 firstprivate(list) – во всех потоках создаются локальные
копии переменных, которые инициализируются их
значениями до входа в параллельный регион
 lastprivate(list) – во всех потоках создаются локальные
копии переменных. По окончанию работы всех потоков
локальная переменная вне параллельного региона
обновляется значением этой переменной одного из
потоков
 shared(list) – переменные являются общими для всех
потоков
 threadprivate (list)
Атомарные операции
4242
#pragma omp parallel for private(v)
for (i = 0; i < n; i++) {
v = fun(a[i]);
#pragma omp atomic
sum += v;
}
Атомарные операции
4343
#pragma omp parallel for private(v)
for (i = 0; i < n; i++) {
v = fun(a[i]);
#pragma omp atomic
sum += v;
}
 Атомарные операции “легче” критических секций
(не используют блокировки)
 Lock-free algorithms & data structures
Параллельная редукция
4444
#pragma omp parallel for reduction(+:sum)
for (i = 0; i < n; i++) {
sum = sum + fun(a[i]);
}
 Операции директивы reduction:
+, *, -, &, |, ^, &&, ||, max, min
 OpenMP 4.0 поддерживает пользовательские
функции редукции
Директивы синхронизации
4545
#pragma omp parallel
{
/* Code */
#pragma omp barrier
/* Code */
}
 Директива barrier осуществляет ожидание достижения
данной точки программы всеми потоками
#pragma omp flush
4646
#pragma omp parallel
{
/* Code */
#pragma omp flush(a, b)
/* Code */
}
 Принудительно обновляет в памяти значения
переменных (Memory barrier)
 Например, в одном потоке выставляем флаг
(сигнал к действию) для другого
Умножение матриц v1.0
4747
#pragma omp parallel
{
#pragma omp for
for (i = 0; i < N; i++) {
for (j = 0; j < N; j++) {
for (k = 0; k < N; k++) {
c[i][j] = c[i][j] +
a[i][k] * b[k][j];
}
}
}
}
Умножение матриц v1.0
4848
#pragma omp parallel
{
#pragma omp for
for (i = 0; i < N; i++) {
for (j = 0; j < N; j++) {
for (k = 0; k < N; k++) {
c[i][j] = c[i][j] +
a[i][k] * b[k][j];
}
}
}
}
Ошибка!
Переменные j, k – общие для всех потоков!
Умножение матриц v2.0
4949
#pragma omp parallel
{
#pragma omp for shared(a, b, c) private(j, k)
for (i = 0; i < N; i++) {
for (j = 0; j < N; j++) {
for (k = 0; k < N; k++) {
c[i][j] = c[i][j] +
a[i][k] * b[k][j];
}
}
}
}
Пример Primes (sequential code)
5050
start = atoi(argv[1]);
end = atoi(argv[2]);
if ((start % 2) == 0 )
start = start + 1;
nprimes = 0;
if (start <= 2)
nprimes++;
for (i = start; i <= end; i += 2) {
if (is_prime_number(i))
nprimes++;
}
 Программа подсчитывает количество простых чисел
в интервале [start, end]
Пример Primes (serial code)
5151
int is_prime_number(int num)
{
int limit, factor = 3;
limit = (int)(sqrtf((double)num) + 0.5f);
while ((factor <= limit) && (num % factor))
factor++;
return (factor > limit);
}
start = atoi(argv[1]);
end = atoi(argv[2]);
if ((start % 2) == 0 )
start = start + 1;
nprimes = 0;
if (start <= 2)
nprimes++;
#pragma omp parallel for
for (i = start; i <= end; i += 2) {
if (is_prime_number(i))
nprimes++;
}
Пример Primes (parallel code)
5252
start = atoi(argv[1]);
end = atoi(argv[2]);
if ((start % 2) == 0 )
start = start + 1;
nprimes = 0;
if (start <= 2)
nprimes++;
#pragma omp parallel for
for (i = start; i <= end; i += 2) {
if (is_prime_number(i))
nprimes++;
}
Пример Primes (parallel code)
5353
Data race
start = atoi(argv[1]);
end = atoi(argv[2]);
if ((start % 2) == 0 )
start = start + 1;
nprimes = 0;
if (start <= 2)
nprimes++;
#pragma omp parallel for
for (i = start; i <= end; i += 2) {
if (is_prime_number(i))
#pragma omp critical
nprimes++;
}
Пример Primes (parallel code)
5454
start = atoi(argv[1]);
end = atoi(argv[2]);
if ((start % 2) == 0 )
start = start + 1;
nprimes = 0;
if (start <= 2)
nprimes++;
#pragma omp parallel for
for (i = start; i <= end; i += 2) {
if (is_prime_number(i))
#pragma omp critical
nprimes++;
}
Пример Primes (parallel code)
5555
Увеличение счетчика можно
реализовать без блокировки
(Lock-free algorithm)
start = atoi(argv[1]);
end = atoi(argv[2]);
if ((start % 2) == 0 )
start = start + 1;
nprimes = 0;
if (start <= 2)
nprimes++;
#pragma omp parallel for reduction(+:nprimes)
for (i = start; i <= end; i += 2) {
if (is_prime_number(i))
nprimes++;
}
Пример Primes (parallel code)
5656
start = atoi(argv[1]);
end = atoi(argv[2]);
if ((start % 2) == 0 )
start = start + 1;
nprimes = 0;
if (start <= 2)
nprimes++;
#pragma omp parallel for reduction(+:nprimes)
for (i = start; i <= end; i += 2) {
if (is_prime_number(i))
nprimes++;
}
Пример Primes (parallel code)
5757
Время выполнения
is_prime_number(i)
зависит от значения i
#pragma omp parallel for reduction(+:nprimes)
for (i = start; i <= end; i += 2) {
if (is_prime_number(i))
nprimes++;
}
Пример Primes (parallel code)
5858
Поток 0
Поток 1
Поток 2
Поток 3
Поток 4
Время выполнения
потоков различно!
Load Imbalance
Пример Primes (parallel code)
5959
start = atoi(argv[1]);
end = atoi(argv[2]);
if ((start % 2) == 0 )
start = start + 1;
nprimes = 0;
if (start <= 2)
nprimes++;
#pragma omp parallel for schedule(static, 1)
reduction(+:nprimes)
for (i = start; i <= end; i += 2) {
if (is_prime_number(i))
nprimes++;
}
Вычисление числа 𝝅
6060
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
𝜋 = න
0
1
4
1 + 𝑥2
𝑑𝑥 𝜋 ≈ ℎ ෍
𝑖=1
𝑛
4
1 + (ℎ(𝑖 − 0.5))2
ℎ =
1
𝑛
Вычисление числа 𝝅
6161
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
𝜋 = න
0
1
4
1 + 𝑥2
𝑑𝑥 𝜋 ≈ ℎ ෍
𝑖=1
𝑛
4
1 + (ℎ(𝑖 − 0.5))2
ℎ =
1
𝑛
// Последовательная версия
int main() {
// ...
nsteps = 1000000;
step = 1.0 / nsteps;
sum = 0.0;
for (i = 1; i <= nsteps; i++) {
x = (i - 0.5) * step;
sum = sum + 4.0 / (1.0 + x * x);
}
pi = step * sum;
printf("Pi = %.16fn", pi);
return 0;
}
ℎ =
1
𝑛
Вычисление числа 𝝅 (OpenMP)
6262
int main(int argc, char **argv) {
// ...
int nthreads = omp_get_max_threads();
double sumloc[nthreads];
double sum = 0.0;
#pragma omp parallel
{
int tid = omp_get_thread_num();
sumloc[tid] = 0.0;
#pragma omp for nowait
for (int i = 1; i <= nsteps; i++) {
double x = (i - 0.5) * step;
sumloc[tid] += 4.0 / (1.0 + x * x);
}
#pragma omp critical
sum += sumloc[tid];
}
double pi = step * sum;
printf("PI is approximately %.16f, Error is %.16fn",
pi, fabs(pi - PI25DT));
// ...
}
Вычисление числа 𝝅 (OpenMP)
int main(int argc, char **argv) {
// ...
int nthreads = omp_get_max_threads();
double sumloc[nthreads];
double sum = 0.0;
#pragma omp parallel
{
int tid = omp_get_thread_num();
sumloc[tid] = 0.0;
#pragma omp for nowait
for (int i = 1; i <= nsteps; i++) {
double x = (i - 0.5) * step;
sumloc[tid] += 4.0 / (1.0 + x * x);
}
#pragma omp critical
sum += sumloc[tid];
}
double pi = step * sum;
printf("PI is approximately %.16f, Error is %.16fn",
pi, fabs(pi - PI25DT));
// ...
}
Shared memory (RAM)
Core 0 (Thread 0)
Cache
Core 1 (Thread 1)
Cache
MESI (Intel MESIF)
cache coherency protocol
sumloc[0], sumloc[1], …
могут попасть в одну строку кеш-памяти
сacheline обновляется несколькими потоками –
кеширование “отключается”
Cacheline
sumloc[0]
Cacheline
sumloc[1]
False sharing
(ложное разделение)
Вычисление числа 𝝅 (OpenMP) v2
6464
struct threadparams {
double sum;
double padding[7]; // Padding for cacheline size (64 bytes)
};
int main(int argc, char **argv) {
// ...
threadparams sumloc[nthreads] __attribute__ ((aligned(64)));
// double sumloc[nthreads * 8];
double sum = 0.0;
#pragma omp parallel num_threads(nthreads) {
int tid = omp_get_thread_num();
sumloc[tid].sum = 0.0;
#pragma omp for nowait
for (int i = 1; i <= nsteps; i++) {
double x = (i - 0.5) * step;
sumloc[tid].sum += 4.0 / (1.0 + x * x);
}
#pragma omp critical
sum += sumloc[tid].sum;
}
// ...
}
 Avoiding and Identifying False Sharing Among Threads //
http://software.intel.com/en-us/articles/avoiding-and-identifying-false-sharing-among-threads
Вычисление числа 𝝅 (OpenMP) v2.1
6565
// ...
double sum = 0.0;
#pragma omp parallel num_threads(nthreads)
{
double sumloc = 0.0; // Избавились от массива
#pragma omp for nowait
for (int i = 1; i <= nsteps; i++) {
double x = (i - 0.5) * step;
sumloc += 4.0 / (1.0 + x * x);
}
#pragma omp critical
sum += sumloc;
}
double pi = step * sum;
// ...
Директива task (OpenMP 3.0)
6666
int fib(int n)
{
if (n < 2)
return n;
return fib(n - 1) + fib(n - 2);
}
 Директива task создает задачу (легковесный поток)
 Задачи из пула динамически выполняются группой потоков
 Динамическое распределение задача по потокам осуществляется
алгоритмами планирования типа work stealing
 Задач может быть намного больше количества потоков
Директива task (OpenMP 3.0)
6767
int fib(int n)
{
int x, y;
if (n < 2)
return n;
#pragma omp task shared(x, n)
x = fib(n - 1);
#pragma omp task shared(y, n)
y = fib(n - 2);
#pragma omp taskwait
return x + y;
}
#pragma omp parallel
#pragma omp single
val = fib(n);
Каждый
рекурсивный
вызов – это задача
Ожидаем
завершение
дочерних задач
Директива task (OpenMP 3.0)
6868
fib(1) fib(0)
fib(2)
fib(3)
fib(1) fib(1) fib(0)
fib(2)
fib(4)
Легковесные
задачи
(tasks)
0 1 2 3
Очереди (деки) задач
+
пул потоков
операционной системы
Need
tasks
Spawn
Sync
Вложенные параллельные регионы
6969
void level2() {
#pragma omp parallel sections
{
#pragma omp section
{ printf("L2 1 Thread PID %un", (unsigned int)pthread_self()); }
#pragma omp section
{ printf("L2 2 Thread PID %un", (unsigned int)pthread_self()); }
}
}
void level1() {
#pragma omp parallel sections num_threads(2)
{
#pragma omp section
{ printf("L1 1 Thread PID %un", (unsigned int)pthread_self());
level2(); }
#pragma omp section
{ printf("L1 2 Thread PID %un", (unsigned int)pthread_self());
level2(); }
}
}
int main() { omp_set_dynamic(0); omp_set_nested(1); level1(); }
Вложенные параллельные регионы
7070
void level2() {
#pragma omp parallel sections
{
#pragma omp section
{ printf("L2 1 Thread PID %un", (unsigned int)pthread_self()); }
#pragma omp section
{ printf("L2 2 Thread PID %un", (unsigned int)pthread_self()); }
}
}
void level1() {
#pragma omp parallel sections num_threads(2)
{
#pragma omp section
{ printf("L1 1 Thread PID %un", (unsigned int)pthread_self());
level2(); }
#pragma omp section
{ printf("L1 2 Thread PID %un", (unsigned int)pthread_self());
level2(); }
}
}
int main() { omp_set_dynamic(0); omp_set_nested(1); level1(); }
Section 2
Section 1
Section 1
Section 2
PID = 210
PID = 100
PID = 100main()
sections
num_threads(2)
Количество потоков 8
(1 + 1 + 3 + 3)
Поток, создавший параллельный
регион, входит в состав новой
группы (создается n – 1 потоков)
Рекурсивный параллелизм (QuickSort)
7171
void partition(int *v, int& i, int& j, int low, int high) {
i = low;
j = high;
int pivot = v[(low + high) / 2];
do {
while (v[i] < pivot) i++;
while (v[j] > pivot) j--;
if (i <= j) {
std::swap(v[i], v[j]);
i++;
j--;
}
} while (i <= j);
}
void quicksort(int *v, int low, int high) {
int i, j;
partition(v, i, j, low, high);
if (low < j)
quicksort(v, low, j);
if (i < high)
quicksort(v, i, high);
}
QuickSort v1 (nested sections)
7272
omp_set_nested(1); // Enable nested parallel regions
void quicksort_nested(int *v, int low, int high) {
int i, j;
partition(v, i, j, low, high);
#pragma omp parallel sections num_threads(2)
{
#pragma omp section
{
if (low < j) quicksort_nested(v, low, j);
}
#pragma omp section
{
if (i < high) quicksort_nested(v, i, high);
}
}
}
Минусы
 Неограниченная глубина вложенных
параллельных регионов
 Отдельные потоки создаются даже
для сортировки коротких отрезков
[low, high]
QuickSort v2 (max_active_levels)
7373
omp_set_nested(1);
// Maximum allowed number of nested, active parallel regions
omp_set_max_active_levels(4);
void quicksort_nested(int *v, int low, int high) {
int i, j;
partition(v, i, j, low, high);
#pragma omp parallel sections num_threads(2)
{
#pragma omp section
{
if (low < j) quicksort_nested(v, low, j);
}
#pragma omp section
{
if (i < high) quicksort_nested(v, i, high);
}
}
}
QuickSort v3 (пороговое значение)
7474
omp_set_nested(1); // Enable nested parallel regions
mp_set_max_active_levels(4); // Max. number of nested parallel regions
void quicksort_nested(int *v, int low, int high) {
int i, j;
partition(v, i, j, low, high);
if (high - low < threshold || (j - low < threshold ||
high - i < threshold))
{
if (low < j) // Sequential execution
quicksort_nested(v, low, j);
if (i < high)
quicksort_nested(v, i, high);
} else {
#pragma omp parallel sections num_threads(2)
{
#pragma omp section
{ quicksort_nested(v, low, j); }
#pragma omp section
{ quicksort_nested(v, i, high); }
}
}
}
 Короткие интервалы
сортируем последовательным
алгоритмом
 Сокращение накладных
расходов на создание потоков
QuickSort v4 (task)
7575
#pragma omp parallel
{
#pragma omp single
quicksort_tasks(array, 0, size - 1);
}
void quicksort_tasks(int *v, int low, int high) {
int i, j;
partition(v, i, j, low, high);
if (high - low < threshold || (j - low < threshold ||
high - i < threshold)) {
if (low < j)
quicksort_tasks(v, low, j);
if(i < high)
quicksort_tasks(v, i, high);
} else {
#pragma omp task
{ quicksort_tasks(v, low, j); }
quicksort_tasks(v, i, high);
}
}
QuickSort v4 (task + untied)
7676
#pragma omp parallel
{
#pragma omp single
quicksort_tasks(array, 0, size - 1);
}
void quicksort_tasks(int *v, int low, int high) {
int i, j;
partition(v, i, j, low, high);
if (high - low < threshold || (j - low < threshold ||
high - i < threshold)) {
if (low < j)
quicksort_tasks(v, low, j);
if(i < high)
quicksort_tasks(v, i, high);
} else {
// Открепить задачу от потока (задачу может выполнять
// любой поток)
#pragma omp task untied
{ quicksort_tasks(v, low, j); }
quicksort_tasks(v, i, high);
}
}
Блокировки (locks)
7777
 Блокировка, мьютекс (lock, mutex) – это объект синхронизации,
который позволяет ограничить одновременный доступ потоков
к разделяемым ресурсам (реализует взаимное исключение)
 OpenMP: omp_lock_set/omp_lock_unset
 POSIX Pthreads: pthread_mutex_lock/pthread_mutex_unlock
 C++11: std::mutex::lock/std::mutex::unlock
 C11: mtx_lock/mtx_unlock
 Блокировка (lock) может быть рекурсивной (вложенной) –
один поток может захватывать блокировку несколько раз
#include <omp.h>
int main()
{
std::vector<int> vec(1000);
std::fill(vec.begin(), vec.end(), 1);
int counter = 0;
omp_lock_t lock;
omp_init_lock(&lock);
#pragma omp parallel for
for (std::vector<int>::size_type i = 0; i < vec.size(); i++) {
if (vec[i] > 0) {
omp_set_lock(&lock);
counter++;
omp_unset_lock(&lock);
}
}
omp_destroy_lock(&lock);
std::cout << "Counter = " << counter << std::endl;
return 0;
}
Блокировки (locks)
7878
Взаимная блокировка (Deadlock)
7979
 Взаимная блокировка (deadlock, тупик) –
ситуация когда два и более потока находятся в состоянии
бесконечного ожидания ресурсов, захваченных этими
потоками
 Самоблокировка (self deadlock) – ситуация когда поток
пытается повторно захватить блокировку, которую уже
захватил (deadlock возникает если блокировка не
является рекурсивной)
void deadlock_example()
{
#pragma omp sections
{
#pragma omp section
{
omp_lock_t lock1, lock2;
omp_set_lock(&lock1);
omp_set_lock(&lock2);
// Code
omp_unset_lock(&lock2);
omp_unset_lock(&lock1);
}
#pragma omp section
{
omp_lock_t lock1, lock2;
omp_set_lock(&lock2);
omp_set_lock(&lock1);
// Code
omp_unset_lock(&lock1);
omp_unset_lock(&lock2);
}
}
}
Взаимная блокировка (Deadlock)
8080
1. T0
захватывает
Lock1
2. T0 ожидает
Lock2
1. T1
захватывает
Lock2
2. T1 ожидает
Lock1
OpenMP 4.0: Поддержка ускорителей (GPU)
8181
sum = 0;
#pragma omp target device(acc0) in(B,C)
#pragma omp parallel for reduction(+:sum)
for (i = 0; i < N; i++)
sum += B[i] * C[i]
 omp_set_default_device()
 omp_get_default_device()
 omp_get_num_devices()
OpenMP 4.0: SIMD-конструкции
8282
void minex(float *a, float *b, float *c, float *d)
{
#pragma omp parallel for simd
for (i = 0; i < N; i++)
d[i] = min(distsq(a[i], b[i]), c[i]);
}
 SIMD-конструкции для векторизации циклов
(SSE, AVX2, AVX-512, AltiVec, …)
OpenMP 4.0: Thread Affinity
8383
 #pragma omp parallel proc_bind(master | close | spread)
 omp_proc_bind_t omp_get_proc_bind(void)
 Env. variable OMP_PLACES
 Thread affinity – привязка потоков к процессорным ядрам
 export OMP_NUM_THREADS=16
 export OMP_PLACES=0,8,1,9,2,10,3,11,4,12,5,13,6,14,7,15
 export OMP_PROC_BIND=spread,close
#pragma omp declare reduction (merge : std::vector<int> :
omp_out.insert(omp_out.end(),
omp_in.begin(), omp_in.end()
))
void schedule(std::vector<int> &v, std::vector<int> &filtered)
{
#pragma omp parallel for reduction (merge : filtered)
for (std:vector<int>::iterator it = v.begin();
it < v.end(); it++)
{
if (filter(*it))
filtered.push_back(*it);
}
}
OpenMP 4.0: user defined reductions
8484
#pragma omp declare reduction (reduction-identifier :
typename-list : combiner) [identity(identity-expr)]
Литература
8585
 Эхтер Ш., Робертс Дж. Многоядерное программирование. – СПб.: Питер,
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

More Related Content

What's hot

Database , 5 Semantic
Database , 5 SemanticDatabase , 5 Semantic
Database , 5 Semantic
Ali Usman
 
Databases: Locking Methods
Databases: Locking MethodsDatabases: Locking Methods
Databases: Locking Methods
Damian T. Gordon
 
Caballos y frases
Caballos y frasesCaballos y frases
Caballos y frases
Ishtar Cei
 
Access to non local names
Access to non local namesAccess to non local names
Access to non local names
Varsha Kumar
 

What's hot (20)

Database Intergrity
Database IntergrityDatabase Intergrity
Database Intergrity
 
Database , 5 Semantic
Database , 5 SemanticDatabase , 5 Semantic
Database , 5 Semantic
 
Protection Structures & Capabilities in Operating System
Protection Structures & Capabilities in Operating SystemProtection Structures & Capabilities in Operating System
Protection Structures & Capabilities in Operating System
 
Databases: Locking Methods
Databases: Locking MethodsDatabases: Locking Methods
Databases: Locking Methods
 
Nmap basics
Nmap basicsNmap basics
Nmap basics
 
Database Security
Database SecurityDatabase Security
Database Security
 
Simple Network Management Protocol
Simple Network Management ProtocolSimple Network Management Protocol
Simple Network Management Protocol
 
Caballos y frases
Caballos y frasesCaballos y frases
Caballos y frases
 
TCP Vs UDP
TCP Vs UDP TCP Vs UDP
TCP Vs UDP
 
Standard Operating Procedures
Standard Operating ProceduresStandard Operating Procedures
Standard Operating Procedures
 
Ccsk exam cheat sheet
Ccsk exam cheat sheetCcsk exam cheat sheet
Ccsk exam cheat sheet
 
IoT interoperability
IoT interoperabilityIoT interoperability
IoT interoperability
 
Distributed DBMS - Unit 9 - Distributed Deadlock & Recovery
Distributed DBMS - Unit 9 - Distributed Deadlock & RecoveryDistributed DBMS - Unit 9 - Distributed Deadlock & Recovery
Distributed DBMS - Unit 9 - Distributed Deadlock & Recovery
 
Unit 6
Unit 6Unit 6
Unit 6
 
Distributed DBMS - Unit 6 - Query Processing
Distributed DBMS - Unit 6 - Query ProcessingDistributed DBMS - Unit 6 - Query Processing
Distributed DBMS - Unit 6 - Query Processing
 
Bars performance appraisal example
Bars performance appraisal exampleBars performance appraisal example
Bars performance appraisal example
 
Accounts Payable Processing Presentation
Accounts Payable Processing PresentationAccounts Payable Processing Presentation
Accounts Payable Processing Presentation
 
Access to non local names
Access to non local namesAccess to non local names
Access to non local names
 
File organisation
File organisationFile organisation
File organisation
 
Query processing
Query processingQuery processing
Query processing
 

Viewers also liked

Viewers also liked (20)

Лекция 7. Стандарт OpenMP (подолжение)
Лекция 7. Стандарт OpenMP (подолжение)Лекция 7. Стандарт OpenMP (подолжение)
Лекция 7. Стандарт OpenMP (подолжение)
 
Векторизация кода (семинар 2)
Векторизация кода (семинар 2)Векторизация кода (семинар 2)
Векторизация кода (семинар 2)
 
Лекция 7. Декартовы деревья (Treaps, дучи, дерамиды)
Лекция 7. Декартовы деревья (Treaps, дучи, дерамиды)Лекция 7. Декартовы деревья (Treaps, дучи, дерамиды)
Лекция 7. Декартовы деревья (Treaps, дучи, дерамиды)
 
Лекция 6. Фибоначчиевы кучи (Fibonacci heaps)
Лекция 6. Фибоначчиевы кучи (Fibonacci heaps)Лекция 6. Фибоначчиевы кучи (Fibonacci heaps)
Лекция 6. Фибоначчиевы кучи (Fibonacci heaps)
 
Лекция 4. Векторизация кода (Code vectorization: SSE, AVX)
Лекция 4. Векторизация кода (Code vectorization: SSE, AVX)Лекция 4. Векторизация кода (Code vectorization: SSE, AVX)
Лекция 4. Векторизация кода (Code vectorization: SSE, AVX)
 
Лекция 2. Оптимизация ветвлений и циклов (Branch prediction and loop optimiz...
Лекция 2. Оптимизация ветвлений и циклов (Branch prediction and loop optimiz...Лекция 2. Оптимизация ветвлений и циклов (Branch prediction and loop optimiz...
Лекция 2. Оптимизация ветвлений и циклов (Branch prediction and loop optimiz...
 
Векторизация кода (семинар 3)
Векторизация кода (семинар 3)Векторизация кода (семинар 3)
Векторизация кода (семинар 3)
 
Лекция 5. Основы параллельного программирования (Speedup, Amdahl's law, Paral...
Лекция 5. Основы параллельного программирования (Speedup, Amdahl's law, Paral...Лекция 5. Основы параллельного программирования (Speedup, Amdahl's law, Paral...
Лекция 5. Основы параллельного программирования (Speedup, Amdahl's law, Paral...
 
Лекция 3. Оптимизация доступа к памяти (Memory access optimization, cache opt...
Лекция 3. Оптимизация доступа к памяти (Memory access optimization, cache opt...Лекция 3. Оптимизация доступа к памяти (Memory access optimization, cache opt...
Лекция 3. Оптимизация доступа к памяти (Memory access optimization, cache opt...
 
Семинар 11. Параллельное программирование на MPI (часть 4)
Семинар 11. Параллельное программирование на MPI (часть 4)Семинар 11. Параллельное программирование на MPI (часть 4)
Семинар 11. Параллельное программирование на MPI (часть 4)
 
Векторизация кода (семинар 1)
Векторизация кода (семинар 1)Векторизация кода (семинар 1)
Векторизация кода (семинар 1)
 
Семинар 12. Параллельное программирование на MPI (часть 5)
Семинар 12. Параллельное программирование на MPI (часть 5)Семинар 12. Параллельное программирование на MPI (часть 5)
Семинар 12. Параллельное программирование на MPI (часть 5)
 
Семинар 1. Многопоточное программирование на OpenMP (часть 1)
Семинар 1. Многопоточное программирование на OpenMP (часть 1)Семинар 1. Многопоточное программирование на OpenMP (часть 1)
Семинар 1. Многопоточное программирование на OpenMP (часть 1)
 
Лекция 9. Программирование GPU
Лекция 9. Программирование GPUЛекция 9. Программирование GPU
Лекция 9. Программирование GPU
 
Лекция 2. Красно-чёрные деревья (Red-black trees). Скошенные деревья (Splay t...
Лекция 2. Красно-чёрные деревья (Red-black trees). Скошенные деревья (Splay t...Лекция 2. Красно-чёрные деревья (Red-black trees). Скошенные деревья (Splay t...
Лекция 2. Красно-чёрные деревья (Red-black trees). Скошенные деревья (Splay t...
 
Лекция 4. Префиксные деревья (tries, prefix trees)
Лекция 4. Префиксные деревья (tries, prefix trees)Лекция 4. Префиксные деревья (tries, prefix trees)
Лекция 4. Префиксные деревья (tries, prefix trees)
 
Лекция 1. Амортизационный анализ (amortized analysis)
Лекция 1. Амортизационный анализ (amortized analysis)Лекция 1. Амортизационный анализ (amortized analysis)
Лекция 1. Амортизационный анализ (amortized analysis)
 
Лекция 10. Графы. Остовные деревья минимальной стоимости
Лекция 10. Графы. Остовные деревья минимальной стоимостиЛекция 10. Графы. Остовные деревья минимальной стоимости
Лекция 10. Графы. Остовные деревья минимальной стоимости
 
Лекция 3. АВЛ-деревья (AVL trees)
Лекция 3. АВЛ-деревья (AVL trees)Лекция 3. АВЛ-деревья (AVL trees)
Лекция 3. АВЛ-деревья (AVL trees)
 
Лекция 11. Методы разработки алгоритмов
Лекция 11. Методы разработки алгоритмовЛекция 11. Методы разработки алгоритмов
Лекция 11. Методы разработки алгоритмов
 

Similar to Лекция 6. Стандарт OpenMP

Лекция 7: Многопоточное программирование: часть 3 (OpenMP)
Лекция 7: Многопоточное программирование: часть 3 (OpenMP)Лекция 7: Многопоточное программирование: часть 3 (OpenMP)
Лекция 7: Многопоточное программирование: часть 3 (OpenMP)
Mikhail Kurnosov
 
Лекция 7: Фибоначчиевы кучи (Fibonacci heaps)
Лекция 7: Фибоначчиевы кучи (Fibonacci heaps)Лекция 7: Фибоначчиевы кучи (Fibonacci heaps)
Лекция 7: Фибоначчиевы кучи (Fibonacci heaps)
Mikhail Kurnosov
 
Netmap (by luigi rizzo) простой и удобный opensource фреймворк для обработк...
Netmap (by luigi rizzo)   простой и удобный opensource фреймворк для обработк...Netmap (by luigi rizzo)   простой и удобный opensource фреймворк для обработк...
Netmap (by luigi rizzo) простой и удобный opensource фреймворк для обработк...
Ontico
 
20090720 hpc exercise1
20090720 hpc exercise120090720 hpc exercise1
20090720 hpc exercise1
Michael Karpov
 
Лекция 6: Многопоточное программирование: часть 2 (Speedup, Amdahl's law, POS...
Лекция 6: Многопоточное программирование: часть 2 (Speedup, Amdahl's law, POS...Лекция 6: Многопоточное программирование: часть 2 (Speedup, Amdahl's law, POS...
Лекция 6: Многопоточное программирование: часть 2 (Speedup, Amdahl's law, POS...
Mikhail Kurnosov
 
Доклад в Mail.ru 01.11.12
Доклад в Mail.ru 01.11.12Доклад в Mail.ru 01.11.12
Доклад в Mail.ru 01.11.12
Alex Tutubalin
 
Лекция 12 (часть 1): Языки программирования семейства PGAS: Cray Chapel
Лекция 12 (часть 1): Языки программирования семейства PGAS: Cray ChapelЛекция 12 (часть 1): Языки программирования семейства PGAS: Cray Chapel
Лекция 12 (часть 1): Языки программирования семейства PGAS: Cray Chapel
Mikhail Kurnosov
 

Similar to Лекция 6. Стандарт OpenMP (20)

Лекция 7: Многопоточное программирование: часть 3 (OpenMP)
Лекция 7: Многопоточное программирование: часть 3 (OpenMP)Лекция 7: Многопоточное программирование: часть 3 (OpenMP)
Лекция 7: Многопоточное программирование: часть 3 (OpenMP)
 
Лекция 7: Фибоначчиевы кучи (Fibonacci heaps)
Лекция 7: Фибоначчиевы кучи (Fibonacci heaps)Лекция 7: Фибоначчиевы кучи (Fibonacci heaps)
Лекция 7: Фибоначчиевы кучи (Fibonacci heaps)
 
Лекция 6. Стандарт OpenMP
Лекция 6. Стандарт OpenMPЛекция 6. Стандарт OpenMP
Лекция 6. Стандарт OpenMP
 
Семинар 6. Многопоточное программирование на OpenMP (часть 6)
Семинар 6. Многопоточное программирование на OpenMP (часть 6)Семинар 6. Многопоточное программирование на OpenMP (часть 6)
Семинар 6. Многопоточное программирование на OpenMP (часть 6)
 
Netmap (by luigi rizzo) простой и удобный opensource фреймворк для обработк...
Netmap (by luigi rizzo)   простой и удобный opensource фреймворк для обработк...Netmap (by luigi rizzo)   простой и удобный opensource фреймворк для обработк...
Netmap (by luigi rizzo) простой и удобный opensource фреймворк для обработк...
 
CUDA Course 2010 at MSU
CUDA Course 2010 at MSUCUDA Course 2010 at MSU
CUDA Course 2010 at MSU
 
20090720 hpc exercise1
20090720 hpc exercise120090720 hpc exercise1
20090720 hpc exercise1
 
Вебинар: Основы распараллеливания С++ программ при помощи OpenMP
Вебинар: Основы распараллеливания С++ программ при помощи OpenMPВебинар: Основы распараллеливания С++ программ при помощи OpenMP
Вебинар: Основы распараллеливания С++ программ при помощи OpenMP
 
Евгений Крутько — Опыт внедрения технологий параллельных вычислений для повыш...
Евгений Крутько — Опыт внедрения технологий параллельных вычислений для повыш...Евгений Крутько — Опыт внедрения технологий параллельных вычислений для повыш...
Евгений Крутько — Опыт внедрения технологий параллельных вычислений для повыш...
 
Лекция 6: Многопоточное программирование: часть 2 (Speedup, Amdahl's law, POS...
Лекция 6: Многопоточное программирование: часть 2 (Speedup, Amdahl's law, POS...Лекция 6: Многопоточное программирование: часть 2 (Speedup, Amdahl's law, POS...
Лекция 6: Многопоточное программирование: часть 2 (Speedup, Amdahl's law, POS...
 
Доклад в Mail.ru 01.11.12
Доклад в Mail.ru 01.11.12Доклад в Mail.ru 01.11.12
Доклад в Mail.ru 01.11.12
 
11 встреча — Введение в GPGPU (А. Свириденков)
11 встреча — Введение в GPGPU (А. Свириденков)11 встреча — Введение в GPGPU (А. Свириденков)
11 встреча — Введение в GPGPU (А. Свириденков)
 
Лекция 12 (часть 1): Языки программирования семейства PGAS: Cray Chapel
Лекция 12 (часть 1): Языки программирования семейства PGAS: Cray ChapelЛекция 12 (часть 1): Языки программирования семейства PGAS: Cray Chapel
Лекция 12 (часть 1): Языки программирования семейства PGAS: Cray Chapel
 
Руслан Гроховецкий "Как Python стал делать погоду в Яндексе"
Руслан Гроховецкий "Как Python стал делать погоду в Яндексе"Руслан Гроховецкий "Как Python стал делать погоду в Яндексе"
Руслан Гроховецкий "Как Python стал делать погоду в Яндексе"
 
Вечный вопрос измерения времени
Вечный вопрос измерения времениВечный вопрос измерения времени
Вечный вопрос измерения времени
 
Архитектура и программирование потоковых многоядерных процессоров для научных...
Архитектура и программирование потоковых многоядерных процессоров для научных...Архитектура и программирование потоковых многоядерных процессоров для научных...
Архитектура и программирование потоковых многоядерных процессоров для научных...
 
Принципы работы статического анализатора кода PVS-Studio
Принципы работы статического анализатора кода PVS-StudioПринципы работы статического анализатора кода PVS-Studio
Принципы работы статического анализатора кода PVS-Studio
 
Семинар 5. Многопоточное программирование на OpenMP (часть 5)
Семинар 5. Многопоточное программирование на OpenMP (часть 5)Семинар 5. Многопоточное программирование на OpenMP (часть 5)
Семинар 5. Многопоточное программирование на OpenMP (часть 5)
 
Филипп Торчинский «Анализ производительности и отладка приложений с помощью D...
Филипп Торчинский «Анализ производительности и отладка приложений с помощью D...Филипп Торчинский «Анализ производительности и отладка приложений с помощью D...
Филипп Торчинский «Анализ производительности и отладка приложений с помощью D...
 
введение в Gpu
введение в Gpuвведение в Gpu
введение в Gpu
 

More from Mikhail Kurnosov

More from Mikhail Kurnosov (10)

Лекция 5. B-деревья (B-trees, k-way merge sort)
Лекция 5. B-деревья (B-trees, k-way merge sort)Лекция 5. B-деревья (B-trees, k-way merge sort)
Лекция 5. B-деревья (B-trees, k-way merge sort)
 
Семинар 10. Параллельное программирование на MPI (часть 3)
Семинар 10. Параллельное программирование на MPI (часть 3)Семинар 10. Параллельное программирование на MPI (часть 3)
Семинар 10. Параллельное программирование на MPI (часть 3)
 
Семинар 9. Параллельное программирование на MPI (часть 2)
Семинар 9. Параллельное программирование на MPI (часть 2)Семинар 9. Параллельное программирование на MPI (часть 2)
Семинар 9. Параллельное программирование на MPI (часть 2)
 
Лекция 9. Поиск кратчайшего пути в графе
Лекция 9. Поиск кратчайшего пути в графеЛекция 9. Поиск кратчайшего пути в графе
Лекция 9. Поиск кратчайшего пути в графе
 
Семинар 8. Параллельное программирование на MPI (часть 1)
Семинар 8. Параллельное программирование на MPI (часть 1)Семинар 8. Параллельное программирование на MPI (часть 1)
Семинар 8. Параллельное программирование на MPI (часть 1)
 
Лекция 8. Графы. Обходы графов
Лекция 8. Графы. Обходы графовЛекция 8. Графы. Обходы графов
Лекция 8. Графы. Обходы графов
 
Семинар 7. Многопоточное программирование на OpenMP (часть 7)
Семинар 7. Многопоточное программирование на OpenMP (часть 7)Семинар 7. Многопоточное программирование на OpenMP (часть 7)
Семинар 7. Многопоточное программирование на OpenMP (часть 7)
 
Лекция 7. Бинарные кучи. Пирамидальная сортировка
Лекция 7. Бинарные кучи. Пирамидальная сортировкаЛекция 7. Бинарные кучи. Пирамидальная сортировка
Лекция 7. Бинарные кучи. Пирамидальная сортировка
 
Лекция 6. Хеш-таблицы
Лекция 6. Хеш-таблицыЛекция 6. Хеш-таблицы
Лекция 6. Хеш-таблицы
 
Лекция 5. Бинарные деревья поиска
Лекция 5. Бинарные деревья поискаЛекция 5. Бинарные деревья поиска
Лекция 5. Бинарные деревья поиска
 

Лекция 6. Стандарт OpenMP

  • 1. Курносов Михаил Георгиевич E-mail: mkurnosov@gmail.com WWW: www.mkurnosov.net Курс «Высокопроизводительные вычислительные системы» Сибирский государственный университет телекоммуникаций и информатики (Новосибирск) Осенний семестр, 2015 Лекция 6 Стандарт OpenMP
  • 2. Программный инструментарий 22Hardware (Multi-core processors, SMP/NUMA) Kernel thread Process/thread scheduler Системные вызовы (System calls) POSIX Threads 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  С++14 Threads  C11 Threads  C# Threads  Java Threads  Erlang Threads  Haskell Threads
  • 3. Стандарт OpenMP 33  OpenMP (Open Multi-Processing) – стандарт, определяющий набор директив компилятора, библиотечных процедур и переменных среды окружения для создания многопоточных программ  Разрабатывается в рамках OpenMP Architecture Review Board с 1997 года  http://www.openmp.org  OpenMP 2.5 (2005), OpenMP 3.0 (2008), OpenMP 3.1 (2011), OpenMP 4.0 (2013)  Требуется поддержка со стороны компилятора
  • 4. OpenMP 44 Compiler Information GNU GCC Option: –fopenmp gcc 4.2 – OpenMP 2.5, gcc 4.4 – OpenMP 3.0, gcc 4.7 – OpenMP 3.1 gcc 4.9 – OpenMP 4.0 Clang (LLVM) OpenMP 3.1 Clang + Intel OpenMP RTL http://clang-omp.github.io/ Intel C/C++, Fortran OpenMP 4.0 Option: –Qopenmp, –openmp Oracle Solaris Studio C/C++/Fortran OpenMP 4.0 Option: –xopenmp Microsoft Visual Studio C++ Option: /openmp OpenMP 2.0 only Other compilers: IBM XL, PathScale, PGI, Absoft Pro, … http://openmp.org/wp/openmp-compilers/
  • 5. Модель выполнения OpenMP-программы 55 0 1 0 0 1 2 0 Динамическое управление потоками в модели Fork-Join:  Fork – порождение нового потока  Join – ожидание завершения потока (объединение потоков управления)  OpenMP-программа – совокупность последовательных участков кода (serial code) и параллельных регионов (parallel region)  Каждый поток имеет логический номер: 0, 1, 2, …  Главный поток (master) имеет номер 0  Параллельные регионы могут быть вложенными (nested) Parallel region Parallel region Barrier Barrier
  • 6. Пример OpenMP-программы 66 #include <stdio.h> #include <omp.h> int main(int argc, char **argv) { #pragma omp parallel /* <-- Fork */ { printf("Hello, multithreaded world: thread %d of %dn", omp_get_thread_num(), omp_get_num_threads()); } /* <-- Barrier & join */ return 0; }
  • 7. Компиляция и запуск OpenMP-программы 77  По умолчанию количество потоков в параллельном регионе равно числу логических процессоров в системе  Порядок выполнения потоков заранее неизвестен – определяется планировщиком операционной системы $ gcc –fopenmp –o hello ./hello.c $ ./hello Hello, multithreaded world: thread 0 of 4 Hello, multithreaded world: thread 1 of 4 Hello, multithreaded world: thread 3 of 4 Hello, multithreaded world: thread 2 of 4
  • 8. Указание числа потоков 88 $ export OMP_NUM_THREADS=8 $ ./hello Hello, multithreaded world: thread 1 of 8 Hello, multithreaded world: thread 2 of 8 Hello, multithreaded world: thread 3 of 8 Hello, multithreaded world: thread 0 of 8 Hello, multithreaded world: thread 4 of 8 Hello, multithreaded world: thread 5 of 8 Hello, multithreaded world: thread 6 of 8 Hello, multithreaded world: thread 7 of 8
  • 9. Задание числа потоков 99 #include <stdio.h> #include <omp.h> int main(int argc, char **argv) { #pragma omp parallel num_threads(6) { printf("Hello, multithreaded world: thread %d of %dn", omp_get_thread_num(), omp_get_num_threads()); } return 0; }
  • 10. Указание числа потоков 1010 $ export OMP_NUM_THREADS=8 $ ./hello Hello, multithreaded world: thread 2 of 6 Hello, multithreaded world: thread 3 of 6 Hello, multithreaded world: thread 1 of 6 Hello, multithreaded world: thread 0 of 6 Hello, multithreaded world: thread 4 of 6 Hello, multithreaded world: thread 5 of 6  Директива num_threads имеет приоритет над значением переменной среды окружения OMP_NUM_THREADS
  • 11. Список потоков процесса 1111 #include <stdio.h> #include <omp.h> #include <time.h> int main(int argc, char **argv) { #pragma omp parallel num_threads(6) { printf("Hello, multithreaded world: thread %d of %dn", omp_get_thread_num(), omp_get_num_threads()); /* Sleep for 30 seconds */ nanosleep(&(struct timespec){.tv_sec = 30}, NULL); } return 0; }
  • 12. Список потоков процесса 1212 $ ./hello & $ ps -eLo pid,tid,psr,args | grep hello 6157 6157 0 ./hello 6157 6158 1 ./hello 6157 6159 0 ./hello 6157 6160 1 ./hello 6157 6161 0 ./hello 6157 6162 1 ./hello 6165 6165 2 grep hello  Номер процесса (PID)  Номер потока (TID)  Логический процессор (PSR)  Название исполняемого файла  Информация о логических процессорах системы:  /proc/cpuinfo  /sys/devices/system/cpu
  • 13. Условная компиляция 1313 #include <stdio.h> #include <omp.h> int main(int argc, char **argv) { #ifdef _OPENMP #pragma omp parallel num_threads(6) { printf("Hello, multithreaded world: thread %d of %dn", omp_get_thread_num(), omp_get_num_threads()); } printf("OpenMP version %dn", _OPENMP); if (_OPENMP >= 201107) printf(" OpenMP 3.1 is supportedn"); #endif return 0; }
  • 14. Синтаксис директив OpenMP 1414  Языки С/C++ #pragma omp directive-name [clause[ [,] clause]...] new-line #pragma omp parallel  Fortran sentinel directive-name [clause[[,] clause]...] !$omp parallel  Создание потоков  Распределение вычислений между потоками  Управление пространством видимости переменных  Синхронизация потоков  …
  • 15. Создание потоков (parallel) 1515 #pragma omp parallel { /* Этот код выполняется всеми потоками */ } #pragma omp parallel if (expr) { /* Код выполняется потоками если expr = true */ } #pragma omp parallel num_threads(n / 2) { /* Код выполняется n / 2 потоками */ } На выходе из параллельного региона осуществляется барьерная синхронизация – все потоки ждут последнего
  • 16. Создание потоков (sections) 1616 #pragma omp parallel sections { #pragma omp section { /* Код потока 0 */ } #pragma omp section { /* Код потока 1 */ } } При любых условиях выполняется фиксированное количество потоков (по количеству секций)
  • 17. Функции runtime-библиотеки 1717  int omp_get_thread_num() – возвращает номер текущего потока  int omp_get_num_threads() – возвращает количество потоков в параллельном регионе  void omp_set_num_threads(int n)  double omp_get_wtime()
  • 18. Директива master 1818 #pragma omp parallel { /* Этот код выполняется всеми потоками */ #pragma omp master { /* Код выполняется только потоком 0 */ } /* Этот код выполняется всеми потоками */ }
  • 19. Директива single 1919 #pragma omp parallel { /* Этот код выполняется всеми потоками */ #pragma omp single { /* Код выполняется только одним потоком */ } /* Этот код выполняется всеми потоками */ }
  • 20. Директива for (data parallelism) 2020 #define N 13 #pragma omp parallel { #pragma omp for for (i = 0; i < N; i++) { printf("Thread %d i = %dn", omp_get_thread_num(), i); } } Итерации цикла распределяются между потоками 0 1 2 3 4 5 6 7 8 9 10 11 12i: Thread 0 Thread 1 Thread 2 Thread 3
  • 21. Директива for 2121 $ OMP_NUM_THREADS=4 ./prog Thread 2 i = 7 Thread 2 i = 8 Thread 2 i = 9 Thread 0 i = 0 Thread 0 i = 1 Thread 0 i = 2 Thread 3 i = 10 Thread 3 i = 11 Thread 3 i = 12 Thread 0 i = 3 Thread 1 i = 4 Thread 1 i = 5 Thread 1 i = 6
  • 22. Алгоритмы распределения итераций 22 #define N 13 #pragma omp parallel { #pragma omp for schedule(static, 2) for (i = 0; i < N; i++) { printf("Thread %d i = %dn", omp_get_thread_num(), i); } } 0 1 2 3 4 5 6 7 8 9 10 11 12i: Итерации цикла распределяются циклически (round-robin) блоками по 2 итерации T0 T1 T2 T3 T0 T1 T2
  • 23. Алгоритмы распределения итераций 2323 $ OMP_NUM_THREADS=4 ./prog Thread 0 i = 0 Thread 0 i = 1 Thread 0 i = 8 Thread 0 i = 9 Thread 1 i = 2 Thread 1 i = 3 Thread 1 i = 10 Thread 1 i = 11 Thread 3 i = 6 Thread 3 i = 7 Thread 2 i = 4 Thread 2 i = 5 Thread 2 i = 12
  • 24. Алгоритм Описание static, m Цикл делится на блоки по m итераций (до выполнения), которые распределяются по потокам dynamic, m Цикл делится на блоки по m итераций. При выполнении блока из m итераций поток выбирает следующий блок из общего пула guided, m Блоки выделяются динамически. При каждом запросе размер блока уменьшается экспоненциально до m runtime Алгоритм задается пользователем через переменную среды OMP_SCHEDULE Алгоритмы распределения итераций 2424
  • 25. Директива for (ordered) 2525 #define N 7 #pragma omp parallel { #pragma omp for ordered for (i = 0; i < N; i++) { #pragma omp ordered printf("Thread %d i = %dn", omp_get_thread_num(), i); } }  Директива ordered организует последовательное выполнение итераций (i = 0, 1, …) – синхронизация  Поток с i = k ожидает пока потоки с i = k – 1, k – 2, … не выполнят свои итерации
  • 26. Директива for (ordered) 2626 $ OMP_NUM_THREADS=4 ./prog Thread 0 i = 0 Thread 0 i = 1 Thread 1 i = 2 Thread 1 i = 3 Thread 2 i = 4 Thread 2 i = 5 Thread 3 i = 6
  • 27. Директива for (nowait) 2727 #define N 7 #pragma omp parallel { #pragma omp for nowait for (i = 0; i < N; i++) { printf("Thread %d i = %dn", omp_get_thread_num(), i); } }  По окончанию цикла потоки не выполняют барьерную синхронизацию  Конструкция nowait применима и к директиве sections
  • 28. Директива for (collapse) 2828 #define N 3 #define M 4 #pragma omp parallel { #pragma omp for collapse(2) for (i = 0; i < N; i++) { for (j = 0; j < M; j++) printf("Thread %d i = %dn", omp_get_thread_num(), i); } }  сollapse(n) объединяет пространство итераций n циклов в одно 0 1 2 0 1 2 3i: j: 0,0 0,1 0,2 0,3 1,0 1,1 1,2 1,3 2,0 2,1 2,2 2,3 T0 + T1 T2 T3
  • 29. Директива for (collapse) 2929 $ OMP_NUM_THREADS=4 ./prog Thread 2 i = 1 Thread 2 i = 1 Thread 2 i = 2 Thread 0 i = 0 Thread 0 i = 0 Thread 0 i = 0 Thread 3 i = 2 Thread 3 i = 2 Thread 3 i = 2 Thread 1 i = 0 Thread 1 i = 1 Thread 1 i = 1
  • 30. #include <iostream> #include <vector> int main() { std::vector<int> vec(1000); std::fill(vec.begin(), vec.end(), 1); int counter = 0; #pragma omp parallel for for (std::vector<int>::size_type i = 0; i < vec.size(); i++) { if (vec[i] > 0) { counter++; } } std::cout << "Counter = " << counter << std::endl; return 0; } Ошибки в многопоточных программах 30
  • 31. Ошибки в многопоточных программах 3131 $ g++ –fopenmp –o ompprog ./ompprog.cpp $ ./omprog Counter = 381 $ ./omprog Counter = 909 $ ./omprog Counter = 398 На каждом запуске итоговое значение Counter разное! Правильный результат Counter = 1000
  • 32. #include <iostream> #include <vector> int main() { std::vector<int> vec(1000); std::fill(vec.begin(), vec.end(), 1); int counter = 0; #pragma omp parallel for for (std::vector<int>::size_type i = 0; i < vec.size(); i++) { if (vec[i] > 0) { counter++; } } std::cout << "Counter = " << counter << std::endl; return 0; } Ошибки в многопоточных программах 32 Потоки осуществляют конкурентный доступ к переменной counter – одновременно читают её и записывают
  • 33. Thread 0 Thread 1 Memory (counter) 0 movl [counter], %eax ← 0 incl %eax 0 movl %eax, [counter] → 1 movl [counter], %eax ← 1 incl %eax 1 movl %eax, [counter] → 2 Состояние гонки (Race condition, data race) 33 #pragma omp parallel { counter++; } movl [counter], %eax incl %eax movl %eax, [counter] C++ Идеальная последовательность выполнения инструкций 2-х потоков counter = 2
  • 34. Thread 0 Thread 1 Memory (counter) 0 movl [counter], %eax ← 0 incl %eax movl [counter], %eax ← 0 movl %eax, [counter] incl %eax → 1 movl %eax, [counter] → 1 1 Состояние гонки (Race condition, data race) 34 movl [counter], %eax incl %eax movl %eax, [counter] C++ Возможная последовательность выполнения инструкций 2-х потоков counter = 1 Error: Data race #pragma omp parallel { counter++; }
  • 35. Состояние гонки (Race condition, data race) 35  Состояние гонки (Race condition, data race) – это состояние программы, в которой несколько потоков одновременно конкурируют за доступ к общей структуре данных (для чтения/записи)  Порядок выполнения потоков заранее не известен – носит случайный характер  Планировщик динамически распределяет процессорное время учитывая текущую загруженность процессорных ядер, а нагрузку (потоки, процессы) создают пользователи, поведение которых носит случайных характер  Состояние гонки данных (Race condition, data race) трудно обнаруживается в программах и воспроизводится в тестах  Состояние гонки данных (Race condition, data race) – это типичный пример Гейзенбага (Heisenbug)
  • 36. Обнаружение состояния гонки (Data race) 36 Динамические анализаторы  Valgrind Helgrind, DRD  Intel Thread Checker  Oracle Studio Thread Analyzer  Java ThreadSanitizer  Java Chord Статические анализаторы кода  PVS-Studio (viva64)  …
  • 37. ==8238== Helgrind, a thread error detector ==8238== Copyright (C) 2007-2012, and GNU GPL'd, by OpenWorks LLP et al. ==8238== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info ==8238== Command: ./ompprog report ... ==8266== ---------------------------------------------------------------- ==8266== Possible data race during write of size 4 at 0x7FEFFD358 by thread #3 ==8266== Locks held: none ==8266== at 0x400E6E: main._omp_fn.0 (ompprog.cpp:14) ==8266== by 0x3F84A08389: ??? (in /usr/lib64/libgomp.so.1.0.0) ==8266== by 0x4A0A245: ??? (in /usr/lib64/valgrind/vgpreload_helgrind-amd64-linux.so) ==8266== by 0x34CFA07C52: start_thread (in /usr/lib64/libpthread-2.17.so) ==8266== by 0x34CF2F5E1C: clone (in /usr/lib64/libc-2.17.so) ==8266== ==8266== This conflicts with a previous write of size 4 by thread #1 ==8266== Locks held: none ==8266== at 0x400E6E: main._omp_fn.0 (ompprog.cpp:14) ==8266== by 0x400CE8: main (ompprog.cpp:11)... Valgrind Helgrind 37 $ g++ -fopenmp -o ompprog ./ompprog.cpp $ valgrind --tool=helgrind ./ompprog
  • 38. Директивы синхронизации 3838  Директивы синхронизации позволяют управлять порядком выполнения заданных участков кода потоками  #pragma omp critical  #pragma omp atomic  #pragma omp ordered  #pragma omp barrier
  • 39. #pragma omp parallel for private(v) for (i = 0; i < n; i++) { v = fun(a[i]); #pragma omp critical { sum += v; } } Критические секции 3939
  • 40. #pragma omp parallel for private(v) for (i = 0; i < n; i++) { v = fun(a[i]); #pragma omp critical { sum += v; } }  Критическая секция (Critical section) – участок кода в многопоточной программе, выполняемый всеми потоками последовательно  Критические секции снижают степень параллелизма Критические секции 4040
  • 41. Управление видимостью переменных 4141  private(list) – во всех потоках создаются локальные копии переменных (начальное значение)  firstprivate(list) – во всех потоках создаются локальные копии переменных, которые инициализируются их значениями до входа в параллельный регион  lastprivate(list) – во всех потоках создаются локальные копии переменных. По окончанию работы всех потоков локальная переменная вне параллельного региона обновляется значением этой переменной одного из потоков  shared(list) – переменные являются общими для всех потоков  threadprivate (list)
  • 42. Атомарные операции 4242 #pragma omp parallel for private(v) for (i = 0; i < n; i++) { v = fun(a[i]); #pragma omp atomic sum += v; }
  • 43. Атомарные операции 4343 #pragma omp parallel for private(v) for (i = 0; i < n; i++) { v = fun(a[i]); #pragma omp atomic sum += v; }  Атомарные операции “легче” критических секций (не используют блокировки)  Lock-free algorithms & data structures
  • 44. Параллельная редукция 4444 #pragma omp parallel for reduction(+:sum) for (i = 0; i < n; i++) { sum = sum + fun(a[i]); }  Операции директивы reduction: +, *, -, &, |, ^, &&, ||, max, min  OpenMP 4.0 поддерживает пользовательские функции редукции
  • 45. Директивы синхронизации 4545 #pragma omp parallel { /* Code */ #pragma omp barrier /* Code */ }  Директива barrier осуществляет ожидание достижения данной точки программы всеми потоками
  • 46. #pragma omp flush 4646 #pragma omp parallel { /* Code */ #pragma omp flush(a, b) /* Code */ }  Принудительно обновляет в памяти значения переменных (Memory barrier)  Например, в одном потоке выставляем флаг (сигнал к действию) для другого
  • 47. Умножение матриц v1.0 4747 #pragma omp parallel { #pragma omp for for (i = 0; i < N; i++) { for (j = 0; j < N; j++) { for (k = 0; k < N; k++) { c[i][j] = c[i][j] + a[i][k] * b[k][j]; } } } }
  • 48. Умножение матриц v1.0 4848 #pragma omp parallel { #pragma omp for for (i = 0; i < N; i++) { for (j = 0; j < N; j++) { for (k = 0; k < N; k++) { c[i][j] = c[i][j] + a[i][k] * b[k][j]; } } } } Ошибка! Переменные j, k – общие для всех потоков!
  • 49. Умножение матриц v2.0 4949 #pragma omp parallel { #pragma omp for shared(a, b, c) private(j, k) for (i = 0; i < N; i++) { for (j = 0; j < N; j++) { for (k = 0; k < N; k++) { c[i][j] = c[i][j] + a[i][k] * b[k][j]; } } } }
  • 50. Пример Primes (sequential code) 5050 start = atoi(argv[1]); end = atoi(argv[2]); if ((start % 2) == 0 ) start = start + 1; nprimes = 0; if (start <= 2) nprimes++; for (i = start; i <= end; i += 2) { if (is_prime_number(i)) nprimes++; }  Программа подсчитывает количество простых чисел в интервале [start, end]
  • 51. Пример Primes (serial code) 5151 int is_prime_number(int num) { int limit, factor = 3; limit = (int)(sqrtf((double)num) + 0.5f); while ((factor <= limit) && (num % factor)) factor++; return (factor > limit); }
  • 52. start = atoi(argv[1]); end = atoi(argv[2]); if ((start % 2) == 0 ) start = start + 1; nprimes = 0; if (start <= 2) nprimes++; #pragma omp parallel for for (i = start; i <= end; i += 2) { if (is_prime_number(i)) nprimes++; } Пример Primes (parallel code) 5252
  • 53. start = atoi(argv[1]); end = atoi(argv[2]); if ((start % 2) == 0 ) start = start + 1; nprimes = 0; if (start <= 2) nprimes++; #pragma omp parallel for for (i = start; i <= end; i += 2) { if (is_prime_number(i)) nprimes++; } Пример Primes (parallel code) 5353 Data race
  • 54. start = atoi(argv[1]); end = atoi(argv[2]); if ((start % 2) == 0 ) start = start + 1; nprimes = 0; if (start <= 2) nprimes++; #pragma omp parallel for for (i = start; i <= end; i += 2) { if (is_prime_number(i)) #pragma omp critical nprimes++; } Пример Primes (parallel code) 5454
  • 55. start = atoi(argv[1]); end = atoi(argv[2]); if ((start % 2) == 0 ) start = start + 1; nprimes = 0; if (start <= 2) nprimes++; #pragma omp parallel for for (i = start; i <= end; i += 2) { if (is_prime_number(i)) #pragma omp critical nprimes++; } Пример Primes (parallel code) 5555 Увеличение счетчика можно реализовать без блокировки (Lock-free algorithm)
  • 56. start = atoi(argv[1]); end = atoi(argv[2]); if ((start % 2) == 0 ) start = start + 1; nprimes = 0; if (start <= 2) nprimes++; #pragma omp parallel for reduction(+:nprimes) for (i = start; i <= end; i += 2) { if (is_prime_number(i)) nprimes++; } Пример Primes (parallel code) 5656
  • 57. start = atoi(argv[1]); end = atoi(argv[2]); if ((start % 2) == 0 ) start = start + 1; nprimes = 0; if (start <= 2) nprimes++; #pragma omp parallel for reduction(+:nprimes) for (i = start; i <= end; i += 2) { if (is_prime_number(i)) nprimes++; } Пример Primes (parallel code) 5757 Время выполнения is_prime_number(i) зависит от значения i
  • 58. #pragma omp parallel for reduction(+:nprimes) for (i = start; i <= end; i += 2) { if (is_prime_number(i)) nprimes++; } Пример Primes (parallel code) 5858 Поток 0 Поток 1 Поток 2 Поток 3 Поток 4 Время выполнения потоков различно! Load Imbalance
  • 59. Пример Primes (parallel code) 5959 start = atoi(argv[1]); end = atoi(argv[2]); if ((start % 2) == 0 ) start = start + 1; nprimes = 0; if (start <= 2) nprimes++; #pragma omp parallel for schedule(static, 1) reduction(+:nprimes) for (i = start; i <= end; i += 2) { if (is_prime_number(i)) nprimes++; }
  • 60. Вычисление числа 𝝅 6060 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 𝜋 = න 0 1 4 1 + 𝑥2 𝑑𝑥 𝜋 ≈ ℎ ෍ 𝑖=1 𝑛 4 1 + (ℎ(𝑖 − 0.5))2 ℎ = 1 𝑛
  • 61. Вычисление числа 𝝅 6161 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 𝜋 = න 0 1 4 1 + 𝑥2 𝑑𝑥 𝜋 ≈ ℎ ෍ 𝑖=1 𝑛 4 1 + (ℎ(𝑖 − 0.5))2 ℎ = 1 𝑛 // Последовательная версия int main() { // ... nsteps = 1000000; step = 1.0 / nsteps; sum = 0.0; for (i = 1; i <= nsteps; i++) { x = (i - 0.5) * step; sum = sum + 4.0 / (1.0 + x * x); } pi = step * sum; printf("Pi = %.16fn", pi); return 0; } ℎ = 1 𝑛
  • 62. Вычисление числа 𝝅 (OpenMP) 6262 int main(int argc, char **argv) { // ... int nthreads = omp_get_max_threads(); double sumloc[nthreads]; double sum = 0.0; #pragma omp parallel { int tid = omp_get_thread_num(); sumloc[tid] = 0.0; #pragma omp for nowait for (int i = 1; i <= nsteps; i++) { double x = (i - 0.5) * step; sumloc[tid] += 4.0 / (1.0 + x * x); } #pragma omp critical sum += sumloc[tid]; } double pi = step * sum; printf("PI is approximately %.16f, Error is %.16fn", pi, fabs(pi - PI25DT)); // ... }
  • 63. Вычисление числа 𝝅 (OpenMP) int main(int argc, char **argv) { // ... int nthreads = omp_get_max_threads(); double sumloc[nthreads]; double sum = 0.0; #pragma omp parallel { int tid = omp_get_thread_num(); sumloc[tid] = 0.0; #pragma omp for nowait for (int i = 1; i <= nsteps; i++) { double x = (i - 0.5) * step; sumloc[tid] += 4.0 / (1.0 + x * x); } #pragma omp critical sum += sumloc[tid]; } double pi = step * sum; printf("PI is approximately %.16f, Error is %.16fn", pi, fabs(pi - PI25DT)); // ... } Shared memory (RAM) Core 0 (Thread 0) Cache Core 1 (Thread 1) Cache MESI (Intel MESIF) cache coherency protocol sumloc[0], sumloc[1], … могут попасть в одну строку кеш-памяти сacheline обновляется несколькими потоками – кеширование “отключается” Cacheline sumloc[0] Cacheline sumloc[1] False sharing (ложное разделение)
  • 64. Вычисление числа 𝝅 (OpenMP) v2 6464 struct threadparams { double sum; double padding[7]; // Padding for cacheline size (64 bytes) }; int main(int argc, char **argv) { // ... threadparams sumloc[nthreads] __attribute__ ((aligned(64))); // double sumloc[nthreads * 8]; double sum = 0.0; #pragma omp parallel num_threads(nthreads) { int tid = omp_get_thread_num(); sumloc[tid].sum = 0.0; #pragma omp for nowait for (int i = 1; i <= nsteps; i++) { double x = (i - 0.5) * step; sumloc[tid].sum += 4.0 / (1.0 + x * x); } #pragma omp critical sum += sumloc[tid].sum; } // ... }  Avoiding and Identifying False Sharing Among Threads // http://software.intel.com/en-us/articles/avoiding-and-identifying-false-sharing-among-threads
  • 65. Вычисление числа 𝝅 (OpenMP) v2.1 6565 // ... double sum = 0.0; #pragma omp parallel num_threads(nthreads) { double sumloc = 0.0; // Избавились от массива #pragma omp for nowait for (int i = 1; i <= nsteps; i++) { double x = (i - 0.5) * step; sumloc += 4.0 / (1.0 + x * x); } #pragma omp critical sum += sumloc; } double pi = step * sum; // ...
  • 66. Директива task (OpenMP 3.0) 6666 int fib(int n) { if (n < 2) return n; return fib(n - 1) + fib(n - 2); }  Директива task создает задачу (легковесный поток)  Задачи из пула динамически выполняются группой потоков  Динамическое распределение задача по потокам осуществляется алгоритмами планирования типа work stealing  Задач может быть намного больше количества потоков
  • 67. Директива task (OpenMP 3.0) 6767 int fib(int n) { int x, y; if (n < 2) return n; #pragma omp task shared(x, n) x = fib(n - 1); #pragma omp task shared(y, n) y = fib(n - 2); #pragma omp taskwait return x + y; } #pragma omp parallel #pragma omp single val = fib(n); Каждый рекурсивный вызов – это задача Ожидаем завершение дочерних задач
  • 68. Директива task (OpenMP 3.0) 6868 fib(1) fib(0) fib(2) fib(3) fib(1) fib(1) fib(0) fib(2) fib(4) Легковесные задачи (tasks) 0 1 2 3 Очереди (деки) задач + пул потоков операционной системы Need tasks Spawn Sync
  • 69. Вложенные параллельные регионы 6969 void level2() { #pragma omp parallel sections { #pragma omp section { printf("L2 1 Thread PID %un", (unsigned int)pthread_self()); } #pragma omp section { printf("L2 2 Thread PID %un", (unsigned int)pthread_self()); } } } void level1() { #pragma omp parallel sections num_threads(2) { #pragma omp section { printf("L1 1 Thread PID %un", (unsigned int)pthread_self()); level2(); } #pragma omp section { printf("L1 2 Thread PID %un", (unsigned int)pthread_self()); level2(); } } } int main() { omp_set_dynamic(0); omp_set_nested(1); level1(); }
  • 70. Вложенные параллельные регионы 7070 void level2() { #pragma omp parallel sections { #pragma omp section { printf("L2 1 Thread PID %un", (unsigned int)pthread_self()); } #pragma omp section { printf("L2 2 Thread PID %un", (unsigned int)pthread_self()); } } } void level1() { #pragma omp parallel sections num_threads(2) { #pragma omp section { printf("L1 1 Thread PID %un", (unsigned int)pthread_self()); level2(); } #pragma omp section { printf("L1 2 Thread PID %un", (unsigned int)pthread_self()); level2(); } } } int main() { omp_set_dynamic(0); omp_set_nested(1); level1(); } Section 2 Section 1 Section 1 Section 2 PID = 210 PID = 100 PID = 100main() sections num_threads(2) Количество потоков 8 (1 + 1 + 3 + 3) Поток, создавший параллельный регион, входит в состав новой группы (создается n – 1 потоков)
  • 71. Рекурсивный параллелизм (QuickSort) 7171 void partition(int *v, int& i, int& j, int low, int high) { i = low; j = high; int pivot = v[(low + high) / 2]; do { while (v[i] < pivot) i++; while (v[j] > pivot) j--; if (i <= j) { std::swap(v[i], v[j]); i++; j--; } } while (i <= j); } void quicksort(int *v, int low, int high) { int i, j; partition(v, i, j, low, high); if (low < j) quicksort(v, low, j); if (i < high) quicksort(v, i, high); }
  • 72. QuickSort v1 (nested sections) 7272 omp_set_nested(1); // Enable nested parallel regions void quicksort_nested(int *v, int low, int high) { int i, j; partition(v, i, j, low, high); #pragma omp parallel sections num_threads(2) { #pragma omp section { if (low < j) quicksort_nested(v, low, j); } #pragma omp section { if (i < high) quicksort_nested(v, i, high); } } } Минусы  Неограниченная глубина вложенных параллельных регионов  Отдельные потоки создаются даже для сортировки коротких отрезков [low, high]
  • 73. QuickSort v2 (max_active_levels) 7373 omp_set_nested(1); // Maximum allowed number of nested, active parallel regions omp_set_max_active_levels(4); void quicksort_nested(int *v, int low, int high) { int i, j; partition(v, i, j, low, high); #pragma omp parallel sections num_threads(2) { #pragma omp section { if (low < j) quicksort_nested(v, low, j); } #pragma omp section { if (i < high) quicksort_nested(v, i, high); } } }
  • 74. QuickSort v3 (пороговое значение) 7474 omp_set_nested(1); // Enable nested parallel regions mp_set_max_active_levels(4); // Max. number of nested parallel regions void quicksort_nested(int *v, int low, int high) { int i, j; partition(v, i, j, low, high); if (high - low < threshold || (j - low < threshold || high - i < threshold)) { if (low < j) // Sequential execution quicksort_nested(v, low, j); if (i < high) quicksort_nested(v, i, high); } else { #pragma omp parallel sections num_threads(2) { #pragma omp section { quicksort_nested(v, low, j); } #pragma omp section { quicksort_nested(v, i, high); } } } }  Короткие интервалы сортируем последовательным алгоритмом  Сокращение накладных расходов на создание потоков
  • 75. QuickSort v4 (task) 7575 #pragma omp parallel { #pragma omp single quicksort_tasks(array, 0, size - 1); } void quicksort_tasks(int *v, int low, int high) { int i, j; partition(v, i, j, low, high); if (high - low < threshold || (j - low < threshold || high - i < threshold)) { if (low < j) quicksort_tasks(v, low, j); if(i < high) quicksort_tasks(v, i, high); } else { #pragma omp task { quicksort_tasks(v, low, j); } quicksort_tasks(v, i, high); } }
  • 76. QuickSort v4 (task + untied) 7676 #pragma omp parallel { #pragma omp single quicksort_tasks(array, 0, size - 1); } void quicksort_tasks(int *v, int low, int high) { int i, j; partition(v, i, j, low, high); if (high - low < threshold || (j - low < threshold || high - i < threshold)) { if (low < j) quicksort_tasks(v, low, j); if(i < high) quicksort_tasks(v, i, high); } else { // Открепить задачу от потока (задачу может выполнять // любой поток) #pragma omp task untied { quicksort_tasks(v, low, j); } quicksort_tasks(v, i, high); } }
  • 77. Блокировки (locks) 7777  Блокировка, мьютекс (lock, mutex) – это объект синхронизации, который позволяет ограничить одновременный доступ потоков к разделяемым ресурсам (реализует взаимное исключение)  OpenMP: omp_lock_set/omp_lock_unset  POSIX Pthreads: pthread_mutex_lock/pthread_mutex_unlock  C++11: std::mutex::lock/std::mutex::unlock  C11: mtx_lock/mtx_unlock  Блокировка (lock) может быть рекурсивной (вложенной) – один поток может захватывать блокировку несколько раз
  • 78. #include <omp.h> int main() { std::vector<int> vec(1000); std::fill(vec.begin(), vec.end(), 1); int counter = 0; omp_lock_t lock; omp_init_lock(&lock); #pragma omp parallel for for (std::vector<int>::size_type i = 0; i < vec.size(); i++) { if (vec[i] > 0) { omp_set_lock(&lock); counter++; omp_unset_lock(&lock); } } omp_destroy_lock(&lock); std::cout << "Counter = " << counter << std::endl; return 0; } Блокировки (locks) 7878
  • 79. Взаимная блокировка (Deadlock) 7979  Взаимная блокировка (deadlock, тупик) – ситуация когда два и более потока находятся в состоянии бесконечного ожидания ресурсов, захваченных этими потоками  Самоблокировка (self deadlock) – ситуация когда поток пытается повторно захватить блокировку, которую уже захватил (deadlock возникает если блокировка не является рекурсивной)
  • 80. void deadlock_example() { #pragma omp sections { #pragma omp section { omp_lock_t lock1, lock2; omp_set_lock(&lock1); omp_set_lock(&lock2); // Code omp_unset_lock(&lock2); omp_unset_lock(&lock1); } #pragma omp section { omp_lock_t lock1, lock2; omp_set_lock(&lock2); omp_set_lock(&lock1); // Code omp_unset_lock(&lock1); omp_unset_lock(&lock2); } } } Взаимная блокировка (Deadlock) 8080 1. T0 захватывает Lock1 2. T0 ожидает Lock2 1. T1 захватывает Lock2 2. T1 ожидает Lock1
  • 81. OpenMP 4.0: Поддержка ускорителей (GPU) 8181 sum = 0; #pragma omp target device(acc0) in(B,C) #pragma omp parallel for reduction(+:sum) for (i = 0; i < N; i++) sum += B[i] * C[i]  omp_set_default_device()  omp_get_default_device()  omp_get_num_devices()
  • 82. OpenMP 4.0: SIMD-конструкции 8282 void minex(float *a, float *b, float *c, float *d) { #pragma omp parallel for simd for (i = 0; i < N; i++) d[i] = min(distsq(a[i], b[i]), c[i]); }  SIMD-конструкции для векторизации циклов (SSE, AVX2, AVX-512, AltiVec, …)
  • 83. OpenMP 4.0: Thread Affinity 8383  #pragma omp parallel proc_bind(master | close | spread)  omp_proc_bind_t omp_get_proc_bind(void)  Env. variable OMP_PLACES  Thread affinity – привязка потоков к процессорным ядрам  export OMP_NUM_THREADS=16  export OMP_PLACES=0,8,1,9,2,10,3,11,4,12,5,13,6,14,7,15  export OMP_PROC_BIND=spread,close
  • 84. #pragma omp declare reduction (merge : std::vector<int> : omp_out.insert(omp_out.end(), omp_in.begin(), omp_in.end() )) void schedule(std::vector<int> &v, std::vector<int> &filtered) { #pragma omp parallel for reduction (merge : filtered) for (std:vector<int>::iterator it = v.begin(); it < v.end(); it++) { if (filter(*it)) filtered.push_back(*it); } } OpenMP 4.0: user defined reductions 8484 #pragma omp declare reduction (reduction-identifier : typename-list : combiner) [identity(identity-expr)]
  • 85. Литература 8585  Эхтер Ш., Робертс Дж. Многоядерное программирование. – СПб.: Питер, 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