Switches+EBF=2hrs

Download Report

Transcript Switches+EBF=2hrs

Программирование в OpenMP
ЛЕКЦИЯ №5
Калинина А.П.
Структура OpenMP
• Набор директив компилятора
• Библиотека функций
• Набор переменных окружения
2
Слайд из лекции В.П. Гергеля
Структура OpenMP
3
Слайд openmp.org, Programming OpenMP
OpenMP
«Вилочный» параллелелизм:
• «Мастер» - поток создает команду потоков
• Последовательная программа преображается в
параллельную
«Мастер»
Параллельные регионы
4
Синтаксис OpenMP*
Большинство конструкций OpenMP* являются директивами
компилятора (pragmas).
• Для C и C++ общий вид директив:
#pragma omp construct [clause [clause]…]
5
OpenMP
• Часть компилятора
• Дополнительные указания компилятору в том ключе, каким
образом можно убрать зависимость по данным в конкретном
участке кода и распараллелить способом, стандартным для данного
компилятора
• Наиболее эффективно для реализации явных параллельных
конструкций
• Затраты на синхронизацию кода очень велики – «RunTime»
невыгоден
6
«Метод успешного многопоточного
программирования в OpenMP»
• 40% успеха – выбор удачного алгоритма решения задачи – полный
параллелелизм, без объектов синхронизации, в крайнем случае – на
уровне элементарных операций
• 40% успеха – выбор удачных структур данных
•
упаковка информации, которая будет передаваться потокам на
выполнение, в минимальный объем, («плотный квант информации» или
строка кэша с максимальным КПД)
•
Информация, необходимая для выполнения кода любого региона,
расположена «рядом»
• 15% успеха – оптимизация последовательного кода – ключи
компиляции, оптимизация под процессор, возможность векторизации и
т.д. – возможность этого часто зависит от предыдущих пунктов
• 5% успеха – конструкции OpenMP
7
95% успеха
Оптимизированная последовательная
программ, пригодная для эффективного
распараллеливания
8
Примеры распараллеливания с помощью
OpenMP
• Преобразование Фурье
• Поиск кратчайших путей на разреженном графе
• Умножение матриц (автор кода О. Нечаева, НГУ,
лаборатория Intel)
• Задача Дирихле (код из курса В.П. Гергеля)
9
Преобразование Фурье
• Схема «ветвь» или «бабочка» - поочередное вычисление
всех коэффициентов (распараллеливается) или
одновременное (тоже распараллеливается) ?
• схема «бабочка» с применением возведения в степень
методом побитового сдвига на порядок быстрее «ветви»
• Последовательный код «бабочки»: перегруппировка
данных перед началом вычислений или в конце?
• перегруппировка данных перед началом вычислений дает
ускорение в 1,5 раза
• Конструкции OpenMP – ускорение примерно в 1,9 раза
10
Задание
1. Откройте проект fft_all_variants
2. Аргументы командной строки – степень 2, номер алгоритма,
число потоков. 1,2,3,7 – последовательные алгоритмы, 4,5,6 –
параллельные
•
Алгоритмы 1,4 – «бабочка» последовательная и параллельная с
предварительной перегруппировкой
•
Алгоритмы 1,4 – «бабочка» последовательная и параллельная без
предварительной перегруппировки
•
Алгоритмы 3,6 – «ветвь» последовательная и параллельная без
применения операции возведения в степень методом побитового
сдвига
3. Определите ускорение для всех вариантов
11
Конструкции OpenMP в преобразовании
Фурье
start = rdtsc();
//start time
for( i = 0 ; i < power ; i++ )//steps
{
#pragma omp parallel for private (index)
for( j = 0 ; j < num_points ; j++)
{
index = j << (i + 1);
fft_index(offset, num_points, index, arg,
arg_2, f_Re, f_Im);
}
num_points >>= 1;
offset <<= 1;
}//end of fft algorithm
stop = rdtsc()-start;
12
Создание проекта Debug c OpenMP в
Visual Studio – процесс отладки
1. Создать пустой проект – вкладка «Debug»
2. Вставить в него код из файла файл.cpp
3. Установить:
• Properties-> C/C++ -> Code generation -> Multythreaded Debug
DLL
• Properties-> C/C++ ->Language -> Process OpenMP Directive Generate Parallel Code
Никакой оптимизации не должно быть! Код
«инструментирован отладочной информацией»
13
Создание проекта Release c OpenMP в
Visual Studio – измерение
производительности
1. Создать пустой проект – вкладка «Release»
2. Вставить в него код из файла файл.cpp
3. Установить:
• Properties-> C/C++ -> Code generation -> Multythreaded DLL
• Properties-> C/C++ ->Language -> Process OpenMP Directive Generate Parallel Code
Установить режимы оптимизации!
• Properties-> C/C++ ->Optimization
14
Требования к защищаемой
индивидуальной задаче
• Код:
• несколько вариантов последовательного и параллельного кода
в одном проекте
• возможность вызова любого варианта кода с помощью
аргумента командной строки
• Презентация:
• краткое описание основных последовательных и
параллельных алгоритмов
• Диагностика инструментов
• Результаты измерения времени работы параллельных и
последовательных вариантов
15
Поиск кратчайших путей на разреженном
графе
• Как представлять граф –
• матрицей весов
• одномерными массивами точек, имеющих связи
• Разреженный граф – выгоднее парами смежных точек –
для малой размерности ускорение 20%, большую
размерность матрицей вообще моделировать невозможно
• Алгоритм Дейкстры: что определять в начале:
• является ли кратчайший путь известным - вероятность 50%
• является ли точка соседом «реперной точки» -вероятность
меньше 1% - лучше сначала это условие – 30% ускорения за
счет правильного порядка
16
Конструкции OpenMP для графа
for (i = 1; i < treeData.N; i++)//__evaluation of the shortest path to
the one of the nodes
{
omp_set_num_threads (num_threads);
#pragma omp parallel private(id)
{id = omp_get_thread_num();
Path_Min[id] = Search_minimum (id,
treeData.max, treeData.Path, treeData.N, num_threads );
}
Nomer_nmin = Path_Min[0];
MinimPath = treeData.Path[Nomer_nmin];
for (j = 0; j < num_threads; j++)
{Nomer_test = Path_Min[j];
Path_test = treeData.Path[Nomer_test];
if(Path_test < MinimPath)
{MinimPath = Path_test; Nomer_nmin = Nomer_test;}
17
}//end of search_minimum
Умножение матриц (автор кода О. Нечаева,
НГУ, лаборатория Intel)
• Перед началом вычислений вторую матрицу необходимо
транспонировать – чтобы данные лежали в строке «подряд» - в
C/C++ массивы располагаются по строкам
•
•
Ускорение порядка 2
Делает возможным выигрыш от применения нижеследующего
• одновременный подсчет 4 элементов новой матрицы «размер
строки кэша кратен объему подгружаемой информации»,
векторизация
• Ключи компиляции – сложные инструкции, оптимизация под Intel
процессор
• Ускорение от сделанного – порядка 20
• Конструкции OpenMP – Ускорение в 1.9 раза
18
Код умножения матриц
#pragma omp parallel for private(j,k)
for(int i=0;i<Size;i+=4)
for(int j=0;j<128;j++)
{
for(int k=0;k<Size;k++)
{
C[i][j]+=A[i][k]*B[j][k];
C[i+1][j]+=A[i+1][k]*B[j][k];
C[i+2][j]+=A[i+2][k]*B[j][k];
C[i+3][j]+=A[i+3][k]*B[j][k];
}
19
}
Синтаксис конструкций OpenMP продолжение
20
Параллельные регионы
• Параллельный регион – это структурированный блок кода,
предназначенный для обработки множеством потоков
• Потоки включаются в работу там, где встретилась директива
‘parallel’
• Все потоки, кроме главного, останавливают свою работу при
выходе из параллельного региона
• Данные, описанные вне параллельной области, по умолчанию
считаются общими («разделяемыми») для всех потоков, если нет
специальных указаний («клауз» - private, firstprivate, lastprivate,
threadprivate…
• Перед тем, как выйти из параллельного
региона, все потоки должны завершить
свою работу, «дождаться друг друга»
#pragma omp parallel
{
блок кода
21
}
Переменная окружения для числа потоков
set OMP_NUM_THREADS=4
• Для большинства систем
• # число потоков= #число процессоров
• Это справедливо для компиляторов Intel®
• Для системы с одним процессором и гипертредингом
число потоков необходимо задавать с помощью данной
конструкции (по умолчанию будет 1 поток)
22
Параллелизм для цикла с целым
параметром
Два варианта кода
#pragma omp parallel
{
#pragma omp for
for (i=0;i< MAX; i++)
res[i] = huge();
}
Потоки «делят» выполнение
итераций цикла друг с другом,
например:
«1-ому – выполнение всех
итераций до MAX/2,
23
2 –ому – все от MAX/2 – до
конца»
Для переменной цикла i
каждый поток «создает себе
копию»
-т.е. когда поток №1 изменяет
свою i.копия1,
-поток №2 об этом ничего не
знает
#pragma omp parallel for
for (i=0;i< MAX; i++)
res[i] = huge();
Области видимости данных
OpenMP использует модель общей (разделяемой - shared)
памяти
•
Большинство переменных считаются по умолчанию
разделяемыми.
•
Глобальные переменные являются общими (разделяемыми)
для всех потоков
24
Области видимости данных
Да, но не все разделяется...
• Локальные переменные в функциях, вызываемых из
параллельного региона, являются частными (PRIVATE)
• Автоматические переменные, описанные в пределах
параллельного блока, являются частными (PRIVATE)
• Номер итерации распараллеливаемого цикла является
частным (private)
25
Атрибуты для изменения области
видимости данных по умолчанию
shared(varname,…) – переменные становятся
разделяемыми
private(varname,…)- переменные становятся частными
26
OpenMP*: критическая секция
#pragma omp critical [(lock_name)]
Определяет область «критического кода» в структурном
блоке
Потоки стоят «в очереди»
– только один может
выполнять consum() защита от «гонок данных»
RES_lock – условное
обозначение
защищаемого блока
27
float RES;
#pragma omp parallel
{ float B;
#pragma omp for
for(int i=0; i<niters; i++){
B = big_job(i);
#pragma omp critical (RES_lock)
consum (B, RES);
}
}
OpenMP* «клауза» Reduction
reduction (операция: список)
Переменные в «списке» должны быть общими вне
параллельного региона
Внутри параллельного региона каждый поток создает свою
копию каждой переменной в списке:
• PRIVATE –копия каждой переменной в «списке» создается
каждым потоком и инициализируется в зависимости от типа
«операции»
• Каждый поток независимо вычисляет результат операции для
своей копии на своих данных
• Перед выходом из параллельного региона «операция»
выполняется над всеми копиями всех потоков (например,
сумма = сумма.поток1+сумма.поток2), и переменная снова
становится «shared»
28
Операции C/C++ Reduction
Значения, которыми инициализируются переменные в
«списке», в зависимости от типа операции
Операция
Значение
Операция
Значение
+
0
&
~0
*
1
|
0
-
0
&&
1
^
0
||
0
29
Распределение итераций между потоками
«Клауза» schedule определяет, каким именно образом итерации
цикла с целочисленным параметром должны быть поделены между
потоками
schedule(static [,chunk])
• Блок итераций размером “chunk” выполняет один поток
• Когда все потоки все сделали свой “chunk”, друг друга
подождали, опять также поделили – и т.д.
schedule(dynamic[,chunk])
• Блок итераций размером “chunk” выполняет один поток
• Неважно, что сделали другие, если поток сделал свой “chunk”,
хватает следующую порцию
schedule(guided[,chunk])
• Режим захвата работы динамический, аналогично предыдущему
• Размер захватываемого блока непрерывно уменьшается, но не
может быть меньше “chunk”
30
Условия, при которых выгоден режим
Schedule Clause
STATIC
DYNAMIC
GUIDED
31
Когда применять
Предсказуемая и
одинаковая работа для
итераций
Непредсказуемая, сильно
неоднородное
распределение работы
между итерациями
Специальный вариант
динамического
распределения
Параллельные секции
Независимые участки кода могут
выполняться параллельно
#pragma omp parallel sections
{
#pragma omp section
phase1();
#pragma omp section
phase2();
#pragma omp section
phase3();
}
32
Последовательный
Параллельный
Конструкция: код для одного потока
Определяет блок кода, который может выполнять лишь
один поток
• Выполняет код тот, кто приходит первый
В конце «кода для одного» все потоки ждут друг друга
#pragma omp parallel
{
DoManyThings();
#pragma omp single
{
ExchangeBoundaries();
} // threads wait here for single
DoManyMoreThings();
}
33
Конструкция: код для «мастер - потока»
Выделяет участок кода, который выполняет только
главный поток
В конце этого участка не подразумевается барьера –
потоки «не ждут друг друга»
#pragma omp parallel
{
DoManyThings();
#pragma omp master
{
//если не главный, выполняй
//операторы ниже блока
ExchangeBoundaries();
}
DoManyMoreThings();
}
34
Барьеры
Следующие конструкции подразумевают барьеры
• parallel
• for
• single
Но барьер без необходимости понижает производительность
• Потоки ждут, вместо того, чтобы работать
Убрать барьер можно с помощью клаузы nowait
35
Nowait
#pragma omp for nowait
for(...)
{...};
#pragma single nowait
{ [...] }
Используется, когда потоки выполняют независимые
вычисления
#pragma omp for schedule(dynamic,1) nowait
for(int i=0; i<n; i++)
a[i] = bigFunc1(i);
#pragma omp for schedule(dynamic,1)
for(int j=0; j<m; j++)
b[j] = bigFunc2(j);
36
Барьер
Применяется, если необходимо синхронизовать во времени
работу потоков
#pragma omp parallel shared (A, B, C)
{
DoSomeWork(A,B);
printf(“Processed A into B\n”);
#pragma omp barrier
DoSomeWork(B,C);
printf(“Processed B into C\n”);
}
37
Atomic
Специальный случай критической секции
Применяется, если содержимое ячейки памяти изменяется
достаточно простым образом
#pragma omp parallel for shared(x, y, index, n)
for (i = 0; i < n; i++) {
#pragma omp atomic
x[index[i]] += work1(i);
y[i] += work2(i);
}
38
OpenMP* API
Получить номер потока
int omp_get_thread_num(void);
Определить количество потоков
int omp_get_num_threads(void);
Обычно не используются
• Получаемый код не преображается в последовательный
• Используется при отладке
• Необходим header file
#include <omp.h>
39
OpenMP* - разновидности «private»
• FIRSTPRIVATE
• LASTPRIVATE
• THREADPRIVATE
40
Firstprivate
Инициализация разделяемых переменных перед параллельной
секцией
incr=0;
#pragma omp parallel for firstprivate(incr)
for (I=0;I<=MAX;I++) {
if ((I%2)==0) incr++;
A(I)=incr;
}
41
Lastprivate
«update» разделяемых переменных после выхода из
параллельной секции с последней итерации цикла
void sq2(int n,
double *lastterm)
{
double x; int i;
#pragma omp parallel
#pragma omp for lastprivate(x)
for (i = 0; i < n; i++){
x = a[i]*a[i] + b[i]*b[i];
b[i] = sqrt(x);
}
lastterm = x;
}
42
Threadprivate
Инициализация из главного потока и копия каждому потоку
struct Astruct A;
#pragma omp threadprivate(A)
…
#pragma omp parallel copyin(A)
do_something_to(&A);
…
#pragma omp parallel
do_something_else_to(&A);
43
Private copies of “A”
persist between
regions