Распараллеливание на компьютерах с общей памятью

Содержание

Слайд 2

Часть 3: Распараллеливание на компьютерах с общей памятью Средства программирования для

Часть 3: Распараллеливание на компьютерах с общей памятью

Средства программирования для компьютеров

с общей памятью (OpenMP, TBB, Cilk, OpenCL, OpenACC)
Понятие потока в вычислениях на компьютерах с общей памятью
Особенности параллельных программ для компьютеров с общей памятью
Представление об управляющих конструкциях OpenMP: Shared, Private, FirstPrivate, LastPrivate
Примеры простейших эффективных и неэффективных алгоритмов
Синхронизация параллельных вычислений
Слайд 3

Средства программирования для компьютеров с общей памятью Компьютер с общей памятью

Средства программирования для компьютеров с общей памятью

Компьютер с общей памятью (shared

memory)
Особенность: автоматический обмен информацией через общую память (все ядра могут прочитать любой кусок памяти)
ПОМНИМ: реальная структура процессора отличается от этой модели, поскольку она не учитывает иерархию памяти и скорость каналов, по которым передаются данные и команды
Слайд 4

Средства программирования для компьютеров с общей памятью Основное средство программирования: OpenMP

Средства программирования для компьютеров с общей памятью

Основное средство программирования: OpenMP (система

директив препроцессора, которые сообщают компилятору, какие куски кода можно параллелить). Текущая версия 4.5.
Дополнительные средства программирования:
Pthreads (POSIX) – команды низкого уровня, работают на Линукс-подобных машинах
Winthreads – аналогичные команды для Windows
TBB – С++ библиотека для параллелизации высокого уровня
Cilk – С-подобные команды
Цель большинства из них – упростить процесс параллельного программирования на машинах с общей памятью

Альтернативные подходы к параллелизации: более удобные в использовании и более высокоуровневые

Слайд 5

Понятие потока Поток (нить, thread) – блок команд и данных для

Понятие потока

Поток (нить, thread) – блок команд и данных для исполнения

на одном из исполняющих модулей\ядер (могут быть виртуальными)
Потоки деляться на физические (привязанные к ядрам процессора), виртуальные (привязанные к операционной системе) и программные (порождённые программой)
Реально мы программируем программные потоки, но иногда очень хочется программировать реальные потоки для повышения производительности
Слайд 6

Понятие потока Нужно знать, что существуют механизмы, которые позволяют пытаться установить

Понятие потока

Нужно знать, что существуют механизмы, которые позволяют пытаться установить связь

между реальными, виртуальными и программными потоками (affinity) – либо через функции (GLibc), либо через переменные окружения, понятные программе (собранной компилятором Intel®, например)
Linux: sched_setaffinity(), sched_getaffinity()
Windows: SetThreadAffinityMask(), SetProcessAffinityMask()
“Универсальный” от Intel® (работает с соответствующими процессорами и компиляторами): KMP_AFFINITY=“verbose,proclist=[3,2,1,0]”
Привяка потоков важна для производительности вычислительных программ – иначе программные потоки могут прыгать по разным ядрам процессора и требовать огромных перекачек данных между ядрами!
Слайд 7

Уровни параллелизма 4 уровня Физический (ядра процессора) Виртуальный физический (гиперсрединг, hyperthreading)

Уровни параллелизма

4 уровня
Физический (ядра процессора)
Виртуальный физический (гиперсрединг, hyperthreading)
Системный
Программный
Инженер-программист всегда программирует


только программный уровень
Слайд 8

Упрощённая модель 2-процессорного сервера Физический уровень для 2-процессорного сервера ЯДРО ЯДРО

Упрощённая модель 2-процессорного сервера

Физический уровень для 2-процессорного сервера

ЯДРО

ЯДРО

ЯДРО

КЭШ 1

КЭШ 1

КЭШ 1

КЭШ

1

КЭШ 2

КЭШ 2

ПАМЯТЬ

ПАМЯТЬ

ЯДРО

Слайд 9

Гиперсрединг обычно включён по умолчанию – выключайте, если можете для высокопроизводительных

Гиперсрединг обычно включён по умолчанию – выключайте, если можете
для высокопроизводительных

вычислений

Виртуальный физический (hyperthreading) уровень (тот же сервер)

ЯДРО

ЯДРО

ЯДРО

ЯДРО

КЭШ 1

КЭШ 1

КЭШ 1

КЭШ 1

КЭШ 2

КЭШ 2

ПАМЯТЬ

ПАМЯТЬ

ЯДРО

ЯДРО

ЯДРО

ЯДРО

Слайд 10

Операционная система имеет ограниченную видимость архитектуры Уровень операционной системы (тот же

Операционная система имеет ограниченную видимость архитектуры

Уровень операционной системы (тот же сервер)

ЯДРО

ЯДРО

ЯДРО

ЯДРО

КЭШ

2

ПАМЯТЬ

ЯДРО

ЯДРО

ЯДРО

ЯДРО

Операционная система рассматривает все ядра как одинаковые, о последствиях позже

Слайд 11

Программа практически не видит архитектуры Уровень программы (тот же сервер) ЯДРО

Программа практически не видит архитектуры

Уровень программы (тот же сервер)

ЯДРО

ЯДРО

ЯДРО

ЯДРО

ПАМЯТЬ

ЯДРО

ЯДРО

ЯДРО

ЯДРО

Наивная реализация алгоритма

приводит к
Неустойчивым замерам производительности
(1-ый прогон - 5.45с, 2-ой прогон 6.34с)
Плохая масштабируемость при увеличении числа потоков
(4 потока - 5.23с, 8 потоков - 6.12с)

OpenMP не очень помогает адресовать проблему ясного видения архитектуры

Слайд 12

Выключайте гиперсрединг в БИОСе по возможности Влияние гиперсрединга ЯДРО ЯДРО ЯДРО

Выключайте гиперсрединг в БИОСе по возможности

Влияние гиперсрединга

ЯДРО

ЯДРО

ЯДРО

ЯДРО

КЭШ 1

КЭШ 1

КЭШ 1

КЭШ 1

КЭШ

2

КЭШ 2

ПАМЯТЬ

ПАМЯТЬ

ЯДРО

ЯДРО

ЯДРО

ЯДРО

Поток 1

2 потока работают на 1-м физическом ядре
? выполнение потоков перемежается==
2-кратное превышение числа потоков
над числом вычислительных модулей

Поток 0

Слайд 13

OS treats all HW threads as equal Влияние операционной системы (перетасовка)

OS treats all HW threads as equal

Влияние операционной системы (перетасовка)

ЯДРО

ЯДРО

ЯДРО

ЯДРО

КЭШ 1

КЭШ

1

КЭШ 1

КЭШ 1

КЭШ 2

КЭШ 2

ПАМЯТЬ

ПАМЯТЬ

Кэш
потока 0

Память
потока 0

Потоки перетасовываются регулярно
? дополнительное передача данных в памяти==
неустойчивое время работы программы

Поток 0

Слайд 14

Операционная система видит все потоки как одинаковые Влияние неоднородной (NUMA) памяти

Операционная система видит все потоки как одинаковые

Влияние неоднородной (NUMA) памяти

ЯДРО

ЯДРО

ЯДРО

ЯДРО

КЭШ 1

КЭШ

1

КЭШ 1

КЭШ 1

КЭШ 2

КЭШ 2

ПАМЯТЬ

ПАМЯТЬ

Кэш
потока 0

Память
потока 0

Локальное относительно потока выделение памяти
увеличивает шанс на то, что данные будут находиться
в правильной части неоднородной памяти

Поток 0

Слайд 15

КЭШ 1 Правильное распределение увеличивает производительность программы Влияние распределения потоков по

КЭШ 1

Правильное распределение увеличивает производительность программы

Влияние распределения потоков по ядрам 1

ЯДРО

ЯДРО

ЯДРО

ЯДРО

КЭШ

1

КЭШ 1

КЭШ 1

КЭШ 2

КЭШ 2

ПАМЯТЬ

ПАМЯТЬ

ЯДРО

ЯДРО

ЯДРО

ЯДРО

Поток 0

Поток 1

Взаимодействие через медленный общий кэш 2,
но при двукратном увеличении размера кэша 1

Слайд 16

Правильное распределение увеличивает производительность программы Влияние распределения потоков по ядрам 2

Правильное распределение увеличивает производительность программы

Влияние распределения потоков по ядрам 2

ЯДРО

ЯДРО

ЯДРО

ЯДРО

КЭШ 1

КЭШ

1

КЭШ 1

КЭШ 1

КЭШ 2

КЭШ 2

ПАМЯТЬ

ПАМЯТЬ

ЯДРО

ЯДРО

ЯДРО

ЯДРО

Поток 1

Взаимодействие через межпроцессорный канал,
но доступно в 2 раза больше
кэша 1
кэша 2
памяти

Поток 0

Слайд 17

KMP_AFFINITY главный инструмент для привязки потоков друг к другу Инструменты Intel

KMP_AFFINITY главный инструмент для привязки потоков друг к другу

Инструменты Intel для

решения проблем

Компиляторы Intel умеют устанавливать привязку разных видов потоков друг к другу
Linux*\OS X*
export KMP_AFFINITY=…
Windows*
set KMP_AFFINITY=…
KMP_AFFINITY=verbose (информация о том, как привязаны потоки)
KMP_AFFINITY не работает для процессоров, произведённых не компанией Intel,
но есть аналогичные инструменты

Слайд 18

Работа с установками affinity KMP_AFFINITY=verbose Выдача OMP: Info #179: KMP_AFFINITY: 2

Работа с установками affinity

KMP_AFFINITY=verbose
Выдача
OMP: Info #179: KMP_AFFINITY: 2 packages x 8

cores/pkg x 2 threads/core (16 total cores)
OMP: Info #147: KMP_AFFINITY: Internal thread 0 bound to OS proc set
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31}
OMP: Info #147: KMP_AFFINITY: Internal thread 1 bound to OS proc set
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31}
KMP_AFFINITY=verbose,granularity=fine,compact,1,0
Выдача
OMP: Info #206: KMP_AFFINITY: OS proc to physical thread map:
OMP: Info #171: KMP_AFFINITY: OS proc 0 maps to package 0 core 0 thread 0
OMP: Info #171: KMP_AFFINITY: OS proc 16 maps to package 0 core 0 thread 1
OMP: Info #171: KMP_AFFINITY: OS proc 1 maps to package 0 core 1 thread 0
OMP: Info #171: KMP_AFFINITY: OS proc 17 maps to package 0 core 1 thread 1
OMP: Info #147: KMP_AFFINITY: Internal thread 0 bound to OS proc set {0}
OMP: Info #147: KMP_AFFINITY: Internal thread 1 bound to OS proc set {1}

Гиперсрединг
присутствует

Гиперсрединг включён

Перетасовка потоков включена

Affinity для
производительности

Перетасовка
выключена

KMP_AFFINITY главный инструмент для привязки потоков друг к другу

Слайд 19

Рекомендации Если вы видите Неустойчивое время работы программы Плохую масштабируемость с

Рекомендации

Если вы видите
Неустойчивое время работы программы
Плохую масштабируемость с N на 2N

или с N на N+1 поток
Плохую производительность на >1 и Хорошую производительность на одной машине, плохую – на другой
Хорошую масштабируемость на одной машине, плохую – на другой
обратите внимание на установки affinity!
Слайд 20

Особенности параллельных программ Основа – обычная последовательная программа Дополнена директивами препроцессора,

Особенности параллельных программ

Основа – обычная последовательная программа
Дополнена директивами препроцессора, которые сообщают

компилятору информацию о том, какого рода параллелизм возможен в данном месте программы
Преимущество и недостаток: легко превращает последовательную программу в параллельную (1 код для всех потоков)
Слайд 21

Представление об управляющих конструкциях OpenMP Принцип работы: интерпретация куска программы как

Представление об управляющих конструкциях OpenMP

Принцип работы: интерпретация куска программы как программы

для многих потоков
...
Как при этом выглядит программа? Сейчас узнаем...
Слайд 22

Представление об управляющих конструкциях OpenMP Директивы компилятора: #pragma omp NAME [clause

Представление об управляющих конструкциях OpenMP

Директивы компилятора:
#pragma omp NAME [clause [clause]…] (Си)
$OMP

NAME [clause [clause]…] (Фортран)
Пример
#pragma omp parallel for num_threads(4)
c$OMP PARALLEL DO NUM_THREADS(4)
Хэдер с прототипами функций и типами:
#include
Ключи компиляторов для использования в параллельных программах с OpenMP
-fopenmp (gcc)
-mp (pgi)
/Qopenmp (intel)
Инфо: www.openmp.org (английский), последняя версия 4.5
Слайд 23

Представление об управляющих конструкциях OpenMP Полезные функции void omp_set_num_threads(int num_threads); (Си)

Представление об управляющих конструкциях OpenMP

Полезные функции
void omp_set_num_threads(int num_threads); (Си)
subroutine omp_set_num_threads(num_threads) (Фортран)
integer

num_threads
int omp_get_num_threads(void);
integer function omp_get_num_threads()
int omp_get_max_threads(void);
integer function omp_get_max_threads()
int omp_get_thread_num(void);
integer function omp_get_thread_num()
int omp_in_parallel(void);
logical function omp_in_parallel()
void omp_[set,get]_[nested,dynamic](int var);
subroutine omp_[set,get]_[nested,dynamic] (var)
logical var
Слайд 24

Представление об управляющих конструкциях OpenMP Полезные функции для продвинутого параллелизма void

Представление об управляющих конструкциях OpenMP

Полезные функции для продвинутого параллелизма
void omp_init_lock(omp_lock_t *lock);
void

omp_init_nest_lock(omp_nest_lock_t *lock);
subroutine omp_init_lock(svar)
integer (kind=omp_lock_kind) svar
subroutine omp_init_nest_lock(nvar)
integer (kind=omp_nest_lock_kind) nvar
void omp_destroy_lock(omp_lock_t *lock);
subroutine omp_destroy_lock(svar)
integer (kind=omp_lock_kind) svar
void omp_[set,unset,test]_lock(omp_lock_t *lock);
subroutine omp_[set,unset,test]_lock(svar)
integer (kind=omp_lock_kind) svar
Слайд 25

Представление об управляющих конструкциях OpenMP Пример OpenMP программы #pragma omp parallel

Представление об управляющих конструкциях OpenMP

Пример OpenMP программы
#pragma omp parallel for private(i)

firstprivate(N) shared(a,b) lastprivate(j)
for (i=0; i {
sum+=a[i]*b[i];
}
Слайд 26

Примеры простейших эффективных и неэффективных алгоритмов Скалярное произведение (Версия 0) sum=0.0;

Примеры простейших эффективных и неэффективных алгоритмов

Скалярное произведение (Версия 0)
sum=0.0;
for (int

i=0; i {
sum+=a[i]*b[i];
}
Слайд 27

Примеры простейших эффективных и неэффективных алгоритмов Скалярное произведение (Версия 1) sum=0.0;

Примеры простейших эффективных и неэффективных алгоритмов

Скалярное произведение (Версия 1)
sum=0.0;
#pragma omp parallel

for
for (int i=0; i {
sum+=a[i]*b[i];
}
Слайд 28

Примеры простейших эффективных и неэффективных алгоритмов Скалярное произведение (Версия 2) sum=0.0;

Примеры простейших эффективных и неэффективных алгоритмов

Скалярное произведение (Версия 2)
sum=0.0;
#pragma omp parallel

for
for (int i=0; i {
#pragma omp atomic
sum+=a[i]*b[i];
}
Слайд 29

Примеры простейших эффективных и неэффективных алгоритмов Скалярное произведение (Версия 3) sum=0.0;

Примеры простейших эффективных и неэффективных алгоритмов

Скалярное произведение (Версия 3)
sum=0.0;
#pragma omp parallel

for reduction(+:sum)
for (int i=0; i {
sum+=a[i]*b[i];
}
Слайд 30

Примеры простейших эффективных и неэффективных алгоритмов Скалярное произведение (Версия 4) sum=0.0;

Примеры простейших эффективных и неэффективных алгоритмов

Скалярное произведение (Версия 4)
sum=0.0;
#pragma omp parallel

for reduction(+:sum) num_threads(4)
for (int i=0; i {
sum+=a[i]*b[i];
}
Слайд 31

Примеры простейших эффективных и неэффективных алгоритмов Скалярное произведение (Версия 5) sum=0.0;

Примеры простейших эффективных и неэффективных алгоритмов

Скалярное произведение (Версия 5)
sum=0.0;
#pragma omp parallel

for reduction(+:sum) shared(a,b)
for (int i=0; i {
sum+=a[i]*b[i];
}
Слайд 32

Примеры простейших эффективных и неэффективных алгоритмов Скалярное произведение (Версия 6) sum=0.0;

Примеры простейших эффективных и неэффективных алгоритмов

Скалярное произведение (Версия 6)
sum=0.0;
#pragma omp parallel

for reduction(+:sum) shared(a,b) schedule(dynamic)
for (int i=0; i {
sum+=a[i]*b[i];
}
Слайд 33

Примеры простейших эффективных и неэффективных алгоритмов Скалярное произведение (Версия 7) sum=0.0;

Примеры простейших эффективных и неэффективных алгоритмов

Скалярное произведение (Версия 7)
sum=0.0;
int

M=32;
#pragma omp parallel for reduction(+:sum) shared(a,b)
for (int i=0; i {
for (int j=i*M; j<(i+1)*M; j++)
{
sum+=a[j]*b[j];
}
}
Слайд 34

Примеры простейших эффективных и неэффективных алгоритмов Скалярное произведение (Версия 8) #pragma

Примеры простейших эффективных и неэффективных алгоритмов

Скалярное произведение (Версия 8)
#pragma omp parallel

sections
{
#pragma omp section
{
//printf("I'm thread No. %i\n", omp_get_thread_num());
for (int i=0; i {
sum1+=a[i]*b[i];
}
}
#pragma omp section
{
//printf("I'm thread No. %i\n", omp_get_thread_num());
for (int i=N/2; i {
sum2+=a[i]*b[i];
}
}
}
Слайд 35

Примеры простейших эффективных и неэффективных алгоритмов Итог: Увы, но на другом

Примеры простейших эффективных и неэффективных алгоритмов

Итог:
Увы, но на другом компьютере результаты

могут существенно отличаться от приведённых в таблице выше...
Слайд 36

Синхронизация параллельных вычислений Простейшая конструкция синхронизации: barrier #pragma omp barrier !$omp

Синхронизация параллельных вычислений

Простейшая конструкция синхронизации: barrier
#pragma omp barrier
!$omp barrier
Исполнение параллельного кода

присотанавливается до тех пор, пока все потоки не дойдут до данного места в программе
Польза: Гарантирует, что все потоки продолжат вычисления не раньше, чем закончат предыдущий кусок работы
Проблема: скорость работы программы определяется самым медленным из потоков
Слайд 37

Синхронизация параллельных вычислений loop: #pragma omp parallel for shared(a,b) reduction(+:sum) for

Синхронизация параллельных вычислений

loop:
#pragma omp parallel for shared(a,b) reduction(+:sum)
for (int i=begin;

i {
sum+=a[i]*b[i];
}

2 раза посчитал sum, но параллельно!

Сменилось состояние ОС!

2 раза посчитал sum, но параллельно!

Слайд 38

Синхронизация параллельных вычислений Конструкции Single & Master #pragma omp single\master !$omp

Синхронизация параллельных вычислений

Конструкции Single & Master
#pragma omp single\master
!$omp single\master
Исполнение данной части

кода происходит одним из потоков и содержит барьер неявно (single)\главным потоком без барьера(master)
Польза: Гарантирует исполнение куска кода только одни потоком
Проблема: нужно быть внимательным!
Слайд 39

Синхронизация параллельных вычислений loop: #pragma omp parallel for shared(a,b) reduction(+:sum) for

Синхронизация параллельных вычислений

loop:
#pragma omp parallel for shared(a,b) reduction(+:sum)
for (int i=begin;

i {
sum+=a[i]*b[i];
}

Компилятор поработал

Слайд 40

Синхронизация параллельных вычислений Конструкция Critical #pragma omp critical !$omp critical Исполнение

Синхронизация параллельных вычислений

Конструкция Critical
#pragma omp critical
!$omp critical
Исполнение данной части кода происходит

потоками по очереди
Польза: Гарантирует исполнение куска кода всеми потоками по очереди
Проблема: неявная последовательность в коде
Слайд 41

Синхронизация параллельных вычислений loop: #pragma omp parallel for shared(a,b) reduction(+:sum) for

Синхронизация параллельных вычислений

loop:
#pragma omp parallel for shared(a,b) reduction(+:sum)
for (int i=begin;

i {
sum+=a[i]*b[i];
}

Снова компилятор

Слайд 42

Синхронизация параллельных вычислений Конструкция Flush #pragma omp flush !$omp flush Делает

Синхронизация параллельных вычислений

Конструкция Flush
#pragma omp flush
!$omp flush
Делает видимой всем часть памяти

(переменные), которые принадлежат данному потоку
Польза: Позволяет организовать обмен информацией между потоками
Проблема: требует внимательности!

Flush неявно стоит, например, при входе и выходе из parallel for

Слайд 43

Немного об OpenMP 4.* OpenMP 4.0 – это ответ на вызов

Немного об OpenMP 4.*

OpenMP 4.0 – это ответ на вызов альтернативного

стандарта OpenACC
Дополнения, связанные с использованием вспомогательных устройств типа Intel® Xeon Phi™ или GPGPU всевозможных производителей (device constructs)
Дополнительные возможности по реализации и отладке продвинутого параллелизма (SIMD, depend, proc_bind, user defined reduction, cancel, OMP_DISPLAY_ENV)
Расширенная поддержка Фортрана 2003 и С++
(Не забудьте посмотреть на OpenMP 4+!)
Слайд 44

Резюме Компьютер с общей памятью является простейшим вариантом параллельного компьютера Компьютер

Резюме

Компьютер с общей памятью является простейшим вариантом параллельного компьютера
Компьютер с общей

памятью исполняет потоки команд с данными (threads)
Наиболее устоявшийся подход к программированию для компьютеров с общей памятью – система директив и библиотек OpenMP, но конкуренты ему подрастают и очень активно
Отладка и настройка параллельных алгоритмов усложняется по сравнению с серийным кодом на порядок
Дополнительную сложность при оптимизации создаёт различная архитектура компьютеров с общей памятью
Оптимизация параллельной программы осложняется разным поведением программы в отладочном и в компиляторно(автоматически)-оптимизированном режиме