Upload
others
View
10
Download
0
Embed Size (px)
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