Обзор библиотеки Intel® Threading uilding locks · Уровни...

Preview:

Citation preview

Обзор библиотекиIntel® Threading Building Blocks

Алексей Федотов

SSG/DPD/TCAR/Threading Runtimes

Intel® Threading Building Blocks (Intel® TBB) - это простойспособ помочь вашей программе эффективно использоватьвычислительные ресурсы системы, на которой онавыполняется.

Для чего нужна данная библиотека?

Время

ЗадачаУти

ли

зац

ия

си

сте

мы

Время

Задача

Ути

ли

зац

ия

си

сте

мы

2

А что, без Intel® TBB неэффективно?

“The free lunch is over: A Fundamental Turn Toward Concurrency” – Herb Sutter, Dr. Dobb’s журнал, март 2005 г.

10 ГГц

1 ГГц

100 МГц

10 МГц

1 МГц

’79 ’87 ’95 ’03 ’11

Частота процессора

больше

не возрастает

… но мы хотим, чтобы

приложения исполнялись

быстрее…

Когда нет супергероя, на помощь приходят миллионы рядовых солдат!

3

Уровни параллелизма(аппаратное обеспечение)

• Параллелизм на уровне инструкций (ILP)• Конвейерное исполнение.

• Супер-скалярное исполнение (Hyper-Threading).

• Параллелизм на уровне данных (DLP)• SIMD (Single Instruction Multiple Data) векторная обработка.

• Реализован через использование SSE регистров и инструкций.

• Параллелизм на уровне потоков (TLP)• Многоядерная архитектура.

• Множество сокетов с когерентной кэш-памятью.

• Параллелизм на уровне кластеров (CLP)• Множество платформ, соединённых через сеть.

• Нет аппаратный поддержки когерентности кэш-памяти.

4

Уровни параллелизма(программное обеспечение)

• Передача сообщений

• Обработка событий

• Параллелизм данных

• Вектора, SIMD

• Параллелизм данных

• Параллелизм задач

• Шаблон fork-join

Неструктурированный

Структурированный

Структурированный

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

5

Поддержка параллелизма в С++

С++03 C++11 C++17+ Другие подходы

?

6

Поддержка параллелизма в С++

С++03 C++11 C++17+ Другие подходы

нет

?

7

Поддержка параллелизма в С++

С++03 C++11 C++17+ Другие подходы

нет

нет

?

8

Поддержка параллелизма в С++

С++03 C++11 C++17+ Другие подходы

нет

нет

нет

9

Поддержка параллелизма в С++

С++11 С++17+ Другие подходы

?

10

Поддержка параллелизма в С++

С++11 С++17+ Другие подходы

std::async, std::future

?

11

Поддержка параллелизма в С++

С++11 С++17+ Другие подходы

std::async, std::future

«Вручную»на

std::thread и т.п.

?

12

Поддержка параллелизма в С++

С++11 С++17+ Другие подходы

std::async, std::future ?

«Вручную»на

std::thread и т.п.

Нет

13

Поддержка параллелизма в С++

С++11 С++17+ Другие подходы

std::async, std::future

resumableфункции,

развитие std::asyncи std::future

«Вручную»на

std::thread и т.п.

?

Нет

14

Поддержка параллелизма в С++

С++11 С++17+ Другие подходы

std::async, std::future

resumableфункции,

развитие std::asyncи std::future

«Вручную»на

std::thread и т.п.

task_block,Parallel STL

Нет ?

15

Поддержка параллелизма в С++

С++11 С++17+ Другие подходы

std::async, std::future

resumableфункции,

развитие std::asyncи std::future

?

«Вручную»на

std::thread и т.п.

task_block,Parallel STL

Нет Parallel STL (par_vec)

16

Поддержка параллелизма в С++

С++11 С++17+ Другие подходы

std::async, std::future

resumableфункции,

развитие std::asyncи std::future

MPI*, Microsoft AAL*Intel TBB flow graph,Qualcomm MARE* и

другие

«Вручную»на

std::thread и т.п.

task_block,Parallel STL ?

Нет Parallel STL (par_vec)

17

Поддержка параллелизма в С++

С++11 С++17+ Другие подходы

std::async, std::future

resumableфункции,

развитие std::asyncи std::future

MPI*, Microsoft AAL*Intel TBB flow graph,Qualcomm MARE* и

другие

«Вручную»на

std::thread и т.п.

task_block,Parallel STL

OpenMP*, Intel TBB, Cilk*, Nvidia Thrust

Microsoft PPL*, Qualcomm MARE* и

другие

Нет Parallel STL (par_vec) ?

18

Поддержка параллелизма в С++

С++11 С++17+ Другие подходы

std::async, std::future

resumableфункции,

развитие std::asyncи std::future

MPI*, Microsoft AAL*Intel TBB flow graph,Qualcomm MARE* и

другие

«Вручную»на

std::thread и т.п.

task_block,Parallel STL

OpenMP*, Intel TBB, Cilk*, Nvidia Thrust

Microsoft PPL*, Qualcomm MARE* и

другие

Нет Parallel STL (par_vec)

intrinsics, auto-vectrorization, Intel® Cilk™ Plus, OpenCL*,

Nvidia CUDA*, OpenMP* 4, Open ACC

и другие

19

20

int flag = 0;

void AddHead (struct List *list,

struct Node *node) {

while (flag != 0) /* wait */ ;

flag = 1;

node->next = list->head;

list->head = node;

flag = 0;

}

Сработает на двух потоках?

0

flag

1

flag

0

flag

21

int flag = 0;

void AddHead (struct List *list,

struct Node *node) {

while (flag != 0) /* wait */ ;

flag = 1;

node->next = list->head;

list->head = node;

flag = 0;

}

Сработает на двух потоках?

T10

flag

1

flag

0

flag

22

int flag = 0;

void AddHead (struct List *list,

struct Node *node) {

while (flag != 0) /* wait */ ;

flag = 1;

node->next = list->head;

list->head = node;

flag = 0;

}

Сработает на двух потоках?

T10

flagT2

1

flag

0

flag

23

int flag = 0;

void AddHead (struct List *list,

struct Node *node) {

while (flag != 0) /* wait */ ;

flag = 1;

node->next = list->head;

list->head = node;

flag = 0;

}

Сработает на двух потоках?

T10

flagT2

1

flag

1

flag

24

int flag = 0;

void AddHead (struct List *list,

struct Node *node) {

while (flag != 0) /* wait */ ;

flag = 1;

node->next = list->head;

list->head = node;

flag = 0;

}

Сработает на двух потоках?

T10

flagT2

1

flag

1

flag

25

int flag = 0;

void AddHead (struct List *list,

struct Node *node) {

while (flag != 0) /* wait */ ;

flag = 1;

node->next = list->head;

list->head = node;

flag = 0;

}

Сработает на двух потоках?

T10

flagT2

1

flag

0

flag

26

int flag = 0;

void AddHead (struct List *list,

struct Node *node) {

while (flag != 0) /* wait */ ;

flag = 1;

node->next = list->head;

list->head = node;

flag = 0;

}

Сработает на двух потоках?

T10

flagT2

1

flag

0

flag

27

Гонки данных

Пример: i++;

Поток №1 Поток №2Общийсчётчик

0

Чтение 0

Сложение 0

Запись 1

Чтение 1

Сложение 1

Запись 2

Поток №1 Поток №2Общийсчётчик

0

Чтение 0

Чтение 0

Сложение 0

Сложение 0

Запись 1

Запись 1

28

Чем гонки данных так неприятны?

Программы, в которых есть гонки данных, проявляютнедетерминированность в своём исполнении:

• Иногда дают верный результат

• Иногда дают ошибочный результат

Такие программы обычно работают корректно намаленьком количестве потоков с небольшим наборомданных.

Проблемы проявляются чаще, когда количествопотоков и время выполнения программы возрастает.

Отладка таких программ затруднительна!

29

Критические секции

• Это участки кода, которые выполняются потоками эксклюзивно.

• Реализованы при помощи примитивов синхронизации:

• pthread_mutex (Linux*)

• CRITICAL_SECTION (Windows*)

Критические секции устраняют гонки данных!

Критические секции исполняются последовательно!

Нужно понять, что должно исполняться эксклюзивно!

30

Взаимная блокировка

Потоки ждут некоторого события или условия, которое никогда не произойдёт.

Пример: дорожные пробки.Машины не могут повернуть или развернуться.

31

Взаимная блокировка (пример)

CRITICAL_SECTION cs1;

CRITICAL_SECTION cs2;

int x = 0;

int y = 0;

InitializeCriticalSection(&cs1); // Allocation Site (cs1)

InitializeCriticalSection(&cs2); // Allocation Site (cs2)

EnterCriticalSection(&cs1);

x++;

EnterCriticalSection(&cs2);

y++;

LeaveCriticalSection(&cs2);

LeaveCriticalSection(&cs1);

EnterCriticalSection(&cs2);

y++;

EnterCriticalSection(&cs1);

x++;

LeaveCriticalSection(&cs1);

LeaveCriticalSection(&cs2);

Поток №1 Поток №2

Нарушение порядка захвата

1. EnterCriticalSection(&cs1); в потоке №1

2. EnterCriticalSection(&cs2); в потоке №1

3. EnterCriticalSection(&cs2); в потоке №2

4. EnterCriticalSection(&cs1); в потоке №2

Взаимная блокировка

1. EnterCriticalSection(&cs1); в потоке №1

2. EnterCriticalSection(&cs2); в потоке №2

32

Активная взаимная блокировка

http://stackoverflow.com/a/27997039/2882509

33

Ложное разделение ресурсовнеумышленное разделение данных в кэш-линии

Условия:

Два или более потока «работают» с одной и той же кэш-линией, но с разными адресами в памяти.

Хотя бы один поток пишет в свой адрес памяти, фактически помечая кэш-линию на других ядрах как устаревшую.

Последствия:

При следующем обращении по своему адресу ядру будет необходимо обратиться к памяти снова, чтобы загрузить «свежие» данные из неё.

34

float a[N], b[N];

float localSum[NUM_PROCS];

void* work(int tid) {

for (int j = 0; j < ITERATIONS; j++) {

for (int i = tid; i < N; i+= NUM_PROCS) {

a[i] = i + a[i] * b[i];

localSum[tid] += a[i];

}

}

}

0 1 2 3 4 … N-1a[i]

0 1 2 3 4 … N-1b[i]

thread 1 thread 2

0 1 2 3 4 … 15cacheline

Ложное разделение ресурсов (пример)

Пусть NUM_PROCS = 2:

35

void* work(int tid) {

for (int j = 0; j < ITERATIONS; j++) {

for (int i = ;

i < ; i++)

{

a[i] = i + a[i] * b[i];

}

}

}

Ложное разделение ресурсов (исправленный вариант)

int chunks = N / NUM_PROCS;

tid * chunks

(tid + 1) * chunks

float sum = 0.0f;

localSum[tid] = sum;

sum += a[i];

36

Библиотека для параллельного программирования на C++:

Упрощает написание параллельных алгоритмов и скрывает явное управление потоками;

Автоматически адаптируется к системе и позволяет достигнуть максимальной масштабируемости приложения;

Кроссплатформенная: Windows *, Linux *, Mac OS* и другие

http://threadingbuildingblocks.org/

Intel® Threading Building Blocks(Intel® TBB)

37

Настольные компьютеры

Планшеты

Смарфоны

Ноутбуки

Нетбуки

Встроенное ПО

Кластеры

Рабочие станции

Серверы

Облако/Датацентры Сопроцессоры

Ниша Intel® TBB

MPI

OpenMP

Intel® TBB

38

Структура Intel® TBB

Параллельные алгоритмы и структуры данных

Потоки и синхронизация Управление памятью и задачами

Параллельные алгоритмы

Эффективная реализация

типовых шаблонов параллелизма

Конкурентные контейнеры

Контейнеры в стиле STL с возможностью конкурентного доступа без внешних

блокировок

Планировщик задач

Обеспечивает параллелизм на уровне задач, балансировка методом перехвата

работы

Потоки

Обёртки для

системных вызовов

Другое

Потокобезопасныетаймеры и

поддержка С++ исключений

Масштабируемый менеджер памяти

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

Примитивы синхронизации

Атомарные операции и различного рода мьютексы

Вычислительныйграф (Flow Graph)

Набор классов для описания

вычислительных графов, которые

могут исполняться параллельно

Локальнаяпамять потока

Неограниченное число локальных

переменных потока (TLS)

39

Шаблоны параллельных алгоритмов

Шаблоны параллельных алгоритмов

41

• Fork-join запускает исполнение нескольких задач одновременно и затем дожидается завершения каждой из них• Удобен в применении для

функциональной и рекурсивной декомпозиции• Используется как базовый

блок для построения других шаблонов

Примеры: Сортировка слиянием, быстрая сортировка (Хоара), другие алгоритмы «разделяй-и-властвуй»

Шаблон: Fork-Join

42

Функциональная декомпозиция

int e;

main () {

int x[10], j, k, m; j = f(x, k); m = g(x, k);

...

}

int f(int *x, int k)

{

int a; a = e * x[k] * x[k]; return a;

}

int g(int *x, int k)

{

int a; k = k-1; a = e / x[k]; return a;

}

Поток №0

Поток №1

Статическая переменная: общаяГлобальная для потоков: общая

Локальные переменные функций

43

Fork-Join в Intel® TBB

task_group g;

...

g.run( functor1 );

...

g.run( functor2 );

...

g.wait();

Для небольшого предопределённого количества задач

parallel_invoke( functor1, functor2, ...);

Когда количество задач велико или заранее неизвестно

44

Пример: быстрая сортировка

template<typename I>

void fork_join_qsort(I begin, I end) {

typedef typename

std::iterator_traits<I>::value_type T;

if (begin != end) {

const I pivot = end - 1;

const I middle = std::partition(begin, pivot,

std::bind(std::less<T>(), _1, *pivot));

std::swap(*pivot, *middle);

tbb::parallel_invoke(

fork_join_qsort(begin, middle),

fork_join_qsort(middle + 1, end)

);

}

}

45

Рекурсивный (вложенный) параллелизм

46

Эффективная рекурсия с fork-join

• Легко «вкладывается»

• Накладные расходы делятся между потоками

• Именно так устроены tbb::parallel_for, tbb::parallel_reduce

Рекурсивный fork-join обеспечивает высокую степень параллелизма

47

• Map применяет указанную функцию к каждому элементу из заданного набора• Это может быть некий

набор данных или абстрактный индекс

• В серийной программе это частный случай итерирования –независимые операции.

A = map(f)(B);

Примеры: цветовая коррекция изображений; преобразование координат; трассировка лучей; методы Монте-Карло

Шаблон: Map

48

Параллелизм по данным

Последовательный код

for (int i = N/2; i < N; i++) a[i] = foo(i);

for (int i = 0; i < N/2; i++) a[i] = foo(i);

const int N = 1000;int a[N];for (int i = 0; i < N; i++) a[i] = foo(i);

Локальная памятьРазделяемая память

Поток №0

Поток №1

49

Параллелизм по данным

Разделяемая

память

Поток №0 Поток №2

Поток №1

f ( )

f ( )

f ( )

50

tbb::parallel_for

parallel_for( lower, upper, functor );

Применить functor(i) ко всем i [lower, upper)

Предоставляется в нескольких вариантах

parallel_for( lower, upper, stride, functor );

Применить functor(i), изменяя i с заданным шагом

parallel_for( range, functor );

Применить functor(subrange) для набора subrange из range

51

Пример с parallel_for

void saxpy( float a, float x[], float (&y)[], size_t n ) {

tbb::parallel_for( size_t(0), n, [&]( size_t i ) {

y[i] += a * x[i];

});

}

void saxpy( float a, float x[], float (&y)[], size_t n ) {

size_t gs = std::max( n / 1000, 1000 );

tbb::parallel_for( tbb::blocked_range<size_t>(0,n,gs),

[&]( tbb::blocked_range<size_t> r ) {

for( size_t i = r.begin(); i != r.end(); ++i )

y[i] += a * x[i];

}, tbb::simple_partitioner() );

}

52

Управление распределением работы

parallel_for( range, functor, simple_partitioner() );

affinity_partitioner affp;

parallel_for( range, functor, affp );

parallel_for( range, functor, auto_partitioner() );

Рекурсивное деление на максимально возможную глубину

Глубина деления подбирается динамически

Деление запоминается и по возможности воспроизводится

53

Ещё пример: параллелизм в 2D

tbb::parallel_for(

tbb::blocked_range2d<int>(0,m,0,n),

[&](tbb::blocked_range2d<int> r ) {

int i, j;

for(i=r.rows().begin(); i!=r.rows().end(); ++i)

for(j=r.cols().begin(); j!=r.cols().end(); ++j )

a[i][j] = f(b[i][j]);

});

// serial

for( int i=0; i<m; ++i )

for( int j=0; j<n; ++j )

a[i][j] = f(b[i][j]);

Декомпозиция «плиткой» /*tiling*/ в 2D может приводить к лучшей локальности данных, чем вложенные параллельные циклы в 1D.

54

Если parallel_for не подходит

parallel_for_each( first, last, functor );

Применить functor(*iter) ко всем элементам контейнера

•Параллельная версия std::for_each

•Работает со стандартными контейнерами

parallel_do( first, last, functor );

То же с возможностью добавить работу «на лету»

[]( work_item, parallel_do_feeder& feeder ) {

<обработка полученного work_item>

if( <в процессе создан new_work_item> )

feeder.add( new_work_item );

};

Добавление данных для обработки:

55

• Reduce объединяет, при помощи ассоциативной операции, все элементы набора в один элемент

• Например, reduce можно использовать, чтобы найти сумму элементов или максимальный элемент

b = reduce(f)(B);

Примеры: вычисление агрегатных функций; операции с матрицами; численное интегрирование

Шаблон: Reduce /* свёртка */

56

Reduce в Intel® TBB

enumerable_thread_specific<T> sum;

parallel_for( 0, n, [&]( int i ) {

sum.local() += a[i];

});

T total = sum.combine(std::plus<T>());

T sum = parallel_reduce(

blocked_range<int>(0,n),

0.f,

[&](blocked_range<int> r, T s) -> T {

for( int i=r.begin(); i!=r.end(); ++i )

s += a[i];

return s;

},

std::plus<T>()

);

При помощи класса enumerable_thread_specific

При помощи функции parallel_reduce

57

Способ с enumerable_thread_specific

Подходит, если:

Операция коммутативна

Дорого вычислять свёртку (напр. большой размер операндов)

enumerable_thread_specific<T> sum;

...

parallel_for( 0, n, [&]( int i ) {

sum.local() += a[i];

});

T total = sum.combine(std::plus<T>());

Обращение к локальной копии

Применяет указанную операцию для свёртки

локальных копий

Контейнер для thread-local

представлений

58

Способ с parallel_reduce

Подходит, если

Операция некоммутативна, но ассоциативна

Использование диапазона улучшает производительность

string concat = parallel_reduce(

blocked_range<int>(0, n),

string(),

[&](blocked_range<int> r, string s)->string

{

for( int i=r.begin(); i!=r.end(); ++i )

s += a[i];

return s;

},

std::plus<string>()

);

Свёртка частичных результатов

Нейтральный элемент

Свёртка поддиапазона

Начальное значение для свёртки

59

Пример: поиск наименьшего элемента// Find index of smallest element in a[0...n-1]

int ParallelMinIndex ( const float a[], int n ) {

struct MyMin {float value; int idx;};

const MyMin identity = {FLT_MAX,-1};

MyMin result = tbb::parallel_reduce(

tbb::blocked_range<int>(0,n),

identity,

[&] (tbb::blocked_range<int> r, MyMin current) -> MyMin {

for( int i=r.begin(); i<r.end(); ++i )

if( a[i] < current.value ) {

current.value = a[i];

current.idx = i;

}

return current;

},

[] (const MyMin a, const MyMin b) {

return a.value<b.value? a : b;

});

return result.idx;

}

60

Комментарии к parallel_reduce

Можно указывать необязательный аргумент partitioner

Аналогично parallel_for

Для неассоциативных операций рекомендуется parallel_deterministic_reduce

Воспроизводимый результат для арифметики с плавающей точкой

Но не соответствует результату в серийном коде

Рекомендуется явно указывать гранулярность

Не позволяет задать partitioner

61

• Scan полезен в сценариях, когда данные по сути зависимы.

• Для достижения параллелизма необходимо слегка «извернуться».

• Необходимо, чтобы операция удовлетворяла правилу ассоциативности.

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

Шаблон: Scan /* частичные суммы */

62

int n = 16; int temp = 0;

for(int i = 0; i < n; ++i) {

y[i] = temp;

temp = temp + z[i];

}

y[n] = temp;

Задача: найти частичные суммы

1 2 3 4 5 166 7 8 9 10 11 12 13 14 15z[]

y[] 0 1 3 6 10 15 21 28 36 45 55 66 78 91 105120136

63

Параллелизм в шаблоне scantemplate <class T> class Body {

T sum;

T* const y;

const T* const z;

public:

Body(T* y_,const T* z_):sum(0),z(z_),y(y_){}

T get_sum() const {return sum;}

template<typename Tag>

void operator()(

const blocked_range<int>& r,Tag) {

T temp = sum;

for(int i=r.begin();i<r.end();++i) {

temp = temp+z[i];

if(Tag::is_final_scan())

y[i] = temp;

}

sum = temp;

}

Body(Body& b,split):z(b.z),y(b.y),sum(0){}

void reverse_join(Body& a){sum = a.sum+sum;}

void assign(Body& b) {sum = b.sum;}

};

float DoParallelScan(T y[],const T z[],int n){

Body body(y,z);

parallel_scan(blocked_range<int>(0,n),body);

return body.get_sum();

}

Вр

ем

я

64

Шаблон: Sort /* сортировка */

template<typename RandomAccessIterator>

void parallel_sort( RandomAccessIterator begin,

RandomAccessIterator end );

template<typename RandomAccessIterator, typename Compare>

void parallel_sort( RandomAccessIterator begin,

RandomAccessIterator end,

const Compare& comp );

template<typename Container>

void parallel_sort( Container c );

template<typename Container>

void parallel_sort( Container c, const Compare& comp );

65

Вычислительные графы вIntel® Threading Building Blocks

• Состоит из объекта графа

• Задан набором узлов и рёбер

• Позволяет дождаться завершения всей работы внутри графа

Интерфейс Intel TBB flow graph

Объект графа

Узел графа

Ребро

67

Пользователь задаёт узлы и рёбра, взаимодействует с графом, и дожидается его завершения

Пример «Hello World»

tbb::flow::graph g;

tbb::flow::continue_node< tbb::flow::continue_msg >

h( g, []( const continue_msg & ) { std::cout << “Hello “; } );

tbb::flow::make_edge( h, w );

tbb::flow::continue_node< tbb::flow::continue_msg >

w( g, []( const continue_msg & ) { std::cout << “World\n“; } );

h.try_put(continue_msg());

g.wait_for_all();

f() f()

h w

68

Типы узлов

Функциональныеузлы

f() f() f(x) f(x)

source_node continue_node function_node multifunction_node

Буферизирующиеузлы

buffer_node queue_node priority_queue_node sequencer_node

1 023

Разделяющие и объединяющие

узлы

queueing join reserving join tag matching join split_node indexer_node

Другие

broadcast_node write_once_node overwrite_node limiter_node

Intel TBB Flow Graph позволяет описывать как графы зависимостей, так и граф потока данных

69

Граф зависимостей

Описывает зависимости между вычислительными узлами, но не специфицирует, каким образом передаются данные

Виды вычислительных графов

f()

continue_node

f()

f()

f(x)

f(x)

f(x)

function_node

Граф потока данных

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

70

Пример с нелинейными зависимостямиstruct body {

std::string my_name;

body( const char *name ) : my_name(name) {}

void operator()( continue_msg ) const {

printf("%s\n", my_name.c_str());

}

};

int main() {

graph g;

broadcast_node< continue_msg > start(g);

continue_node< continue_msg > a( g, body("A") );

continue_node< continue_msg > b( g, body("B") );

continue_node< continue_msg > c( g, body("C") );

continue_node< continue_msg > d( g, body("D") );

continue_node< continue_msg > e( g, body("E") );

make_edge( start, a ); make_edge( start, b );

make_edge( a, c ); make_edge( b, c );

make_edge( c, d ); make_edge( a, e );

for (int i = 0; i < 3; ++i ) {

start.try_put( continue_msg() );

g.wait_for_all();

}

return 0;

}

A B

E C

D

A B

E C

D

71

Пример графа, обрабатывающего поток данных

f(x)

f(x)

f(x)squarer

cuber

summer

struct cube {

int operator()(int v) {

return v * v * v;

}

};

class sum {

int &my_sum;

public:

sum( int &s ) : my_sum(s) {}

int operator()( tuple<int, int> v ) {

my_sum += get<0>(v) + get<1>(v);

return my_sum;

}

};

struct square {

int operator()(int v)

{

return v * v;

}

};

72

Пример графа, обрабатывающего поток данныхint main() {

int result = 0;

graph g;

broadcast_node<int> input(g);

function_node<int, int> squarer( g, unlimited, square() );

function_node<int, int> cuber( g, unlimited, cube() );

join_node< tuple<int, int>, queueing > j( g );

function_node< tuple<int, int>, int >

summer( g, serial, sum(result) );

make_edge( input, squarer );

make_edge( input, cuber );

make_edge( squarer, input_port<0>(j) );

make_edge( cuber, input_port<1>(j) );

make_edge( j, summer );

for (int i = 1; i <= 3; ++i)

input.try_put(i);

g.wait_for_all();

printf("Final result is %d\n", result);

return 0;

}

73

Пример графа, обрабатывающего поток данных

f(x)

f(x)

f(x)squarer

cuber

summer1

broadcast_node<int> input(g);

input.try_put(1);

Максимальный параллелизм = 1

74

Пример графа, обрабатывающего поток данных

f(x)

f(x)

f(x)squarer

cuber

summer

1

Максимальный параллелизм = 3

1

2

broadcast_node<int> input(g);

input.try_put(2);

75

Пример графа, обрабатывающего поток данных

f(x)

f(x)

f(x)squarer

cuber

summer

1

Максимальный параллелизм = 5

1

function_node<int, int> squarer( g, unlimited, square() );

function_node<int, int> cuber( g, unlimited, cube() );

2

2

76

Пример графа, обрабатывающего поток данных

f(x)

f(x)

f(x)squarer

cuber

summer

1

Максимальный параллелизм = 5

1

broadcast_node<int> input(g);

input.try_put(3);

2

2

3

77

Пример графа, обрабатывающего поток данных

f(x)

f(x)

f(x)squarer

cuber

summer

1

Максимальный параллелизм = 4

1

join_node< tuple<int, int>, queueing > j( g );

4

2

3

78

Пример графа, обрабатывающего поток данных

f(x)

f(x)

f(x)squarer

cuber

summer

1

Максимальный параллелизм = 6

1

function_node<int, int> squarer( g, unlimited, square() );

function_node<int, int> cuber( g, unlimited, cube() );

4

2

3

3

79

Пример графа, обрабатывающего поток данных

f(x)

f(x)

f(x)squarer

cuber

summer

1

Максимальный параллелизм = 5

1

join_node< tuple<int, int>, queueing > j( g );

4

2

3

3

80

Пример графа, обрабатывающего поток данных

f(x)

f(x)

f(x)squarer

cuber

summer

1

Результат = 0Максимальный параллелизм = 6

4

int result = 0;

function_node< tuple<int, int>, int >

summer( g, serial, sum(result) );

1

2

3

3

81

Пример графа, обрабатывающего поток данных

f(x)

f(x)

f(x)squarer

cuber

summer

Результат = 0Максимальный параллелизм = 4

4

join_node< tuple<int, int>, queueing > j( g );

function_node< tuple<int, int>, int >

summer( g, serial, sum(result) );

1

3

3

1

8

82

Пример графа, обрабатывающего поток данных

f(x)

f(x)

f(x)squarer

cuber

summer

Результат = 0Максимальный параллелизм = 2

4

join_node< tuple<int, int>, queueing > j( g );

function_node< tuple<int, int>, int >

summer( g, serial, sum(result) );

1

1

89

27

83

Пример графа, обрабатывающего поток данных

f(x)

f(x)

f(x)squarer

cuber

summer

Результат = 5Максимальный параллелизм = 2

1

join_node< tuple<int, int>, queueing > j( g );

function_node< tuple<int, int>, int >

summer( g, serial, sum(result) );

8

9

27

84

Пример графа, обрабатывающего поток данных

f(x)

f(x)

f(x)squarer

cuber

summer

Результат = 14Максимальный параллелизм = 2

9

join_node< tuple<int, int>, queueing > j( g );

function_node< tuple<int, int>, int >

summer( g, serial, sum(result) );

27

85

Пример графа, обрабатывающего поток данных

Результат = 50Максимальный параллелизм = 1

g.wait_for_all();

printf("Final result is %d\n", result);

f(x)

f(x)

f(x)squarer

cuber

summer

86

Разложение Холецкого (𝐴 = 𝐿𝐿𝑇)

(a) flow based implementation

(b) dependence based implementation

Aparna Chandramowlishwaran, Kathleen Knobe, and Richard Vuduc, “Performance Evaluation of Concurrent Collections on High-Performance Multicore Computing Systems”, 2010 Symposium on Parallel & Distributed Processing (IPDPS), April 2010.

87

Инструмент с графическим интерфейсом, позволяющий:

Создавать вычислительные узлы и задавать зависимости между ними

Генерировать исходный код на C++

Flow Graph Designer

Позволяет запускать приложения, основанные на Intel TBB flow graph, чтобы:

Собрать информацию времени выполнения

Отобразить топологию графа

Получить информацию о проблемах производительности

Экспериментальная версия доступна на whatif.intel.com

88

Вместо явного указания асинхронности в коде, программист описывает структуру алгоритма; Intel TBB автоматически использует возможности для параллельного исполнения узлов в графе

Планировщик Intel TBB даёт:

автоматическое распределение и динамическую балансировку работы

сочетаемость с другими параллельными конструкциями Intel TBB, например, вложенный параллельный цикл внутри вычислительного узла

Граф сохраняет состояние между запусками и позволяет многократное исполнение

Ограничение Intel TBB flow graph

Не рекомендуется использовать блокирующий API (например, для работы с сетью или диском) внутри узлов графа. В графе может быть использован специальный узел async_node для взаимодействия с внешними активностями

Преимущества Intel TBB flow graph

89

Планировщик задач вIntel® Threading Building Blocks

Планировщик задач методом перехвата работы: принципКаждый поток имеет двухстороннюю очередь задач:

• Вновь созданные задача кладётся в начало очереди.

• Поток обрабатывает задачи, забирая их из начала очереди.

• Если у потока нет работы:

• Выбирается случайный поток-жертва

• Делается попытка забрать задачу из конца очереди потока-жертвы

91

Перехват работы в планировщике задач

Каждый процессор имеет свою рабочую очередь.

P P P P

порождение

Порождениезадачи!

92

Перехват работы в планировщике задач

P P P P

порождениеПорождение

задачи!Порождение

задачи!

порождение

порождение

Порождениезадачи!

Порождение добавляет задачу в очередь.Это горячий путь! Должен быть быстрым!

93

Перехват работы в планировщике задач

P

следующая

P P P

следующая

порождение

Возврат иззадачи!

Возврат иззадачи!

Возврат иззадачи!

Когда задача завершена, из очереди вынимается следующая.

94

Перехват работы в планировщике задач

P

следующая

P P P

Возврат иззадачи!

95

Перехват работы в планировщике задач

P P

перехват

P P

Перехват!

Когда у процессора нет работы, он перехватывает её у другого процессора.Это “холодный путь”, может стоить дороже.

96

Перехват работы в планировщике задач

P P P P

Порождениезадачи!

При достаточном параллелизме, перехваты работы редки, и мы получаем линейное ускорение (без учёта эффектов памяти)

порождение

97

Преимущества двухсторонней очереди

Общие операции дёшевы.Накладные расходы на редких операциях.

Доступ к началу очереди есть только у её потока-владельца.

Нет необходимости в дорогих атомарных операциях.

Локальная работа потока находится в начале очереди.

Она была только что порождена скорей всего ещё в кэше.

Нелокальная работа находится в конце очереди.

Порождена раньше всех, скорей всего, из кэша уже вытеснена.

Если код рекурсивно делит по принципу «разделяй и властвуй», ранние задачи будут больше по размеру.

98

Рекурсивный параллелизм

[Data, Data+N)

[Data, Data+N/2)[Data+N/2, Data+N)

[Data, Data+N/k)

[Data, Data+GrainSize)

Доступные для перехвата задачи

99

Рекурсивный параллелизм

[Data, Data+N)

[Data, Data+N/2)[Data+N/2, Data+N)

[Data, Data+N/k)

[Data, Data+GrainSize) Поток сначала исполняет

задачи «в глубину», такимобразом, пользуясь локальностью данных.

100

Рекурсивный параллелизм

[Data, Data+N)

[Data, Data+N/2)[Data+N/2, Data+N)

[Data, Data+N/k)

[Data, Data+GrainSize)

Другие рабочие потоки первоначально перехватывают работу «в ширину», забирая старые, бОльшие куски задачи.

Поток сначала исполняет задачи «в глубину», таким образом, пользуясь локальностью данных.

101

Рекурсивный параллелизм

[Data, Data+N)

[Data, Data+N/2)[Data+N/2, Data+N)

[Data, Data+N/k)

[Data, Data+GrainSize)

102

Библиотека для параллельного программирования на C++:

Упрощает написание параллельных алгоритмов и скрывает явное управление потоками;

Автоматически адаптируется к системе и позволяет достигнуть максимальной масштабируемости приложения;

Кроссплатформенная: Windows *, Linux *, Mac OS* и другие

http://threadingbuildingblocks.org/

Intel® Threading Building Blocks(Intel® TBB)

103

Backup

105

• Конвейер – цепочка из стадий обработки потока данных

• Некоторые стадии могут иметь состояние

• Можно обрабатывать данные по мере поступления: “online”

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

Шаблон: Pipeline /* конвейер */

106

Конвейер

Разные данные на разных стадиях

Разные данные в одной стадии, если там нет состояния

Данные на выходе могут быть переупорядочены

Может понадобиться буферизация между стадиями

107

parallel_pipeline (

ntoken,

make_filter<void,T>(

filter::serial_in_order,

[&]( flow_control & fc ) -> T{

T item = f();

if( !item ) fc.stop();

return item;

}

) &

make_filter<T,U>(

filter::parallel, g

) &

make_filter<U,void>(

filter:: serial_in_order, h

)

);

f

g

h

Использование конвейера

108

Стадии конвейера

Серийная стадия может поддерживать состояние

make_filter<X,Y>(

filter::serial_in_order,

[&]( X x ) -> Y {

extern int count;

++count;

Y y = bar(x);

return y;

}

)

Параллельная стадия –функциональное преобразование

make_filter<X,Y>(

filter::parallel,

[]( X x ) -> Y {

Y y = foo(x);

return y;

}

)

Преобразование X в Y

Отсутствие «гонок» –ответственность программиста

Данные поступают в порядке, заданном на предыдущей упорядоченной стадии

109

make_filter<X,void>(

filter::serial_in_order,

[&]( X x ) {

cout << x;

}

)

Стадии конвейера: вход и выход

make_filter<void,Y>(

filter::serial_in_order,

[&]( flow_control& fc ) -> Y {

Y y;

cin >> y;

if( cin.fail() ) fc.stop();

return y;

}

)

Тип “из” - void

Стадии могут быть любого

типа

Первая стадия получает

специальный аргумент

Результат передаётся дальше по конвейеру, но

игнорируется после flow_control::stop()

Тип “в” - void

110

Построение конвейера

make_filter<X,Y>(

...

)

&

make_filter<Y,Z>(

...

)

• Стадии соединяются при помощи operator&

Тип данных должен совпадать

X

Y

Z

Алгебра типов

make_filter<T,U>(mode,functor) filter_t<T,U>

filter_t<T,U> & filter_t<U,V> filter_t<T,V>

111

Запуск конвейера

parallel_pipeline( size_t ntoken,

const filter_t<void,void>& filter );

Ограничение на кол-во данных в

обработке

Цепочка стадий voidvoid.

• Один поток проводит данные через множество этапов

• Предпочтение обработке имеющихся элементов

Эффективное использование

кэша

• Функциональная декомпозиция не масштабируется

• Параллельные стадии улучшают ситуацию

• Производительность ограничена серийными стадиями

Масштаби-руемость

112

Bzip2: схема конвейера

Read block

Run-length encoding

Output stream

checksum

bit position

output file ptr

Burrows-Wheeler Transform

Move To Front

Run-length Encoding

Huffman Coding

Checksum

Bit-align

Write block

Input stream

input file ptr

113

Recommended