5. Intel Threading Building Blocks
5
Intel TBB позволяет абстрагироваться от низкоуровневых потоков и распараллеливать программу в терминах параллельно выполняющихся задач(task parallelism)
Задачи TBB “легче” потоков операционной системы
Планировщик TBB использует механизм “work stealing” для распределения задач по потокам
Все компоненты Intel TBB определены в пространстве имен C++ (namespace)“tbb”
6. Компиляция программ с Intel TBB
6
$ g++ –Wall –o prog./prog.cpp –ltbb
C:> icl/MD prog.cpp tbb.lib
GNU/Linux
Microsoft Windows (Intel C++ Compiler)
9. Компиляция и запускtbb_hello
9
$ g++ -Wall -I~/opt/tbb/include
-L~/opt/tbb/lib
-o tbb_hello
./tbb_hello.cpp-ltbb
$ ./tbb_hello
Hello from task 2
Hello from task 1
10. Инициализация библиотеки
10
Любой поток использующий алгоритмыили планировщик TBB должен иметь инициализированный объект tbb::task_scheduler_init
TBB >= 2.2 автоматически инициализирует планировщик
Явная инициализация планировщика позволяет:
управлять когда создается и уничтожается планировщик
устанавливать количество используемых потоков выполнения
устанавливать размер стека для потоков выполнения
12. Инициализация библиотеки
12
Конструктор класса task_scheduler_initпринимает два параметра:
task_scheduler_init(intmax_threads=automatic, stack_size_typethread_stack_size=0);
Допустимые значения параметра max_threads:
task_scheduler_init::automatic – количество потоков определяется автоматически
task_scheduler_init::deferred– инициализация откладывается до явного вызова метода task_scheduler_init::initialize(max_threads)
Положительное целое–количество потоков
13. Инициализация библиотеки
13
#include <iostream>
#include <tbb/task_scheduler_init.h>
intmain()
{
intn = tbb::task_scheduler_init::default_num_threads();
for(intp = 1; p <= n; ++p) {
// Construct task scheduler with p threads
tbb::task_scheduler_initinit(p);
std::cout<< "Is active = "<< init.is_active()
<< std::endl;
}
return0;
}
15. parallel_for
15
voidsaxpy(floata, float*x, float*y, size_tn)
{
for(size_ti= 0; i< n; ++i)
y[i] += a * x[i];
}
parallel_forпозволяет разбить пространство итерации на блоки (chunks), которые обрабатываются разными потоками
Требуется создать класс, в котором перегруженный оператор вызова функции operator()содержит код обработки блока итераций
19. affinity_partitioner
19
Класс affinity_partitionerзапоминает какими потоками выполнялись предыдущие итерации и пытается распределять блоки итераций с учетом этой информации –последовательные блоки назначаются на один и тот же поток для эффективного использования кеш-памяти
intmain()
{
// ...
static affinity_partitionerap;
parallel_for(blocked_range<size_t>(0, n), saxpy_par(a, x, y), ap);
// ...
return 0;
}
25. parallel_sort
25
voidparallel_sort(RandomAccessIteratorbegin,
RandomAccessIteratorend,
constCompare& comp);
parallel_sortпозволяет упорядочивать последовательностиэлементов
Применяется детерминированный алгоритм нестабильной сортировки с трудоемкостью O(nlogn)–алгоритм не гарантирует сохранения порядка следования элементов с одинаковыми ключами
27. Планировщик задач (Task scheduler)
27
Intel TBB позволяет абстрагироваться от реальных потоков операционной системы и разрабатывать программу в терминах параллельных задач (task-based parallel programming)
Запуск TBB-задачи примерно в 18 раз быстрее запуска потока POSIXв GNU/Linux (в Microsoft Windows примерно в 100 разбыстрее)
В отличии от планировщика POSIX-потоков в GNU/Linux планировщик TBBреализует “не справедливую” (unfair) политику распределения задач по потокам
29. intfib_par(intn)
{
intval;
fibtask& t = *new(task::allocate_root()) fibtask(n, &val);
task::spawn_root_and_wait(t);
returnval;
}
Числа Фибоначчи: parallel version
29
allocate_rootвыделяет память под корневую задачу(task)класса fibtask
spawn_root_and_waitзапускает задачу на выполнение и ожидает её завершения
30. classfibtask: publictask{
public:
constintn;
int* constval;
fibtask(intn_, int* val_): n(n_), val(val_) {}
task* execute()
{
if(n < 10) {
*val= fib(n); // Use sequential version
} else{
intx, y;
fibtask& a = *new(allocate_child()) fibtask(n -1, &x);
fibtask& b = *new(allocate_child()) fibtask(n -2, &y);
// ref_count: 2 children + 1 for the wait
set_ref_count(3);
spawn(b);
spawn_and_wait_for_all(a);
*val= x + y;
}
returnNULL;
}
};
Числа Фибоначчи: parallel version
30
spawnзапускает задачу на выполнение и не ожидает её завершения
spawn_and_wait_for_all–запускает задачу и ожидает завершения всех дочерних задач
32. Граф задачи (Task graph)
32
Task A
Depth = 0
Refcount= 2
Task B
Depth = 1
Refcount= 2
Task C
Depth = 2
Refcount= 0
Task D
Depth = 2
Refcount= 0
Task E
Depth = 1
Refcount= 0
33. Планирование задач (Task scheduling)
33
Каждый поток поддерживает дек готовых к выполнению задач (deque, двусторонняя очередь)
Планировщик использует комбинированный алгоритма на основе обход графа задач в ширину и глубину
Task E
Task D
Top: Oldest task
Bottom: Youngest Task
34. Планирование задач (Task scheduling)
34
Листовые узлы в графе задач –это задачи готовые к выполнению (ready task, они не ожидают других)
Потоки могу захватывать (steal) задачи из чужих деков (с их верхнего конца)
Task E
Task D
Top: Oldest task
Bottom: Youngest Task
Top: Oldest task
Bottom: Youngest Task
35. Выбор задачи из дека
35
Задача для выполнения выбирается одним из следующих способов (в порядке уменьшения приоритета):
1.Выбирается задача, на которую возвращен указатель методом executeпредыдущей задачи
2.Выбирается задача с нижнего конца (bottom) дека потока
3.Выбирается первая задача из дека (с его верхнего конца) случайно выбранного потока–work stealing
36. Помещение задачи в дек потока
36
Задачи помещаются в дек с его нижнего конца
В дек помещается задача порожденная методомspawn
Задача может быть направлена на повторное выполнение методом task::recycle_to_reexecute
Задача имеет счетчик ссылок (reference count) равный нулю –все дочерние задачи завершены
37. Потокобезопасныеконтейнеры
37
Intel TBB предоставляет классы контейнеров (concurrent containers), которые корректно могут обновляться из нескольких потоков
Для работы в многопоточной программе со стандартными контейнерами STL доступ к ним необходимо защищать блокировками (мьютексами)
Особенности Intel TBB:
oпри работе с контейнерами применяет алгоритмы не требующие блокировок (lock-free algorithms)
oпри необходимости блокируются лишь небольшие участки кода контейнеров (fine-grained locking)
40. Взаимные исключения (Mutual exclusion)
40
Взаимные исключения (mutual exclusion)позволяют управлять количеством потоков, одновременно выполняющих заданный участок кода
В Intel TBB взаимные исключения реализованы средствами мьютексов(mutexes) и блокировок (locks)
Мьютекс(mutex)–это объект синхронизации, который в любой момент времени может быть захвачен только одним потоком, остальные потоки ожидают его освобождения
41. Свойства мьютексовIntel TBB
41
Scalable
Fair–справедливые мьютексызахватываются в порядке обращения к ним потоков (даже если следующий поток в очереди находится в состоянии сна; несправедливыемьютексымогут быть быстрее)
Recursive–рекурсивные мьютексыпозволяют потоку захватившему мьютексповторно его получить
Yield–при длительном ожидании мьютексапоток периодически проверяет его текущее состояние и снимает себя с процессора (засыпает, в GNU/Linux вызывается sched_yield(),а в Microsoft Windows –SwitchToThread())
Block–потока освобождает процессор до тех пор, пока не освободится мьютекс(такие мьютексырекомендуется использовать при длительных ожиданиях)
42. МьютексыIntel TBB
42
spin_mutex–поток ожидающий освобождения мьютексавыполняет пустой цикл ожидания (busy wait)
spin_mutexрекомендуется использовать для защиты небольших участков кода (нескольких инструкций)
queuing_mutex–scalable, fair, non-recursive, spins in user space
spin_rw_mutex–spin_mutex+ reader lock
mutexи recursive_mutex–это обертки вокруг взаимных исключений операционной системы (Microsoft Windows –CRITICAL_SECTION, GNU/Linux –мьютексыбиблиотеки pthread)
43. МьютексыIntel TBB
43
Mutex
Scalable
Fair
Recursive
LongWait
Size
mutex
OSdep.
OSdep.
No
Blocks
>= 3 words
recursive_mutex
OSdep.
OSdep.
Yes
Blocks
>= 3 words
spin_mutex
No
No
No
Yields
1 byte
queuing_mutex
Yes
Yes
No
Yields
1 word
spin_rw_mutex
No
No
No
Yields
1 word
queuing_rw_mutex
Yes
Yes
No
Yields
1 word
44. spin_mutex
44
ListNode*FreeList;
spin_mutexListMutex;
ListNode*AllocateNode()
{
ListNode*node;
{
// Создать и захватить мьютекс(RAII)
spin_mutex::scoped_locklock(ListMutex);
node = FreeList;
if(node)
FreeList= node->next;
}// Мьютексавтоматически освобождается
if(!node)
node = new ListNode()
returnnode;
}
45. spin_mutex
45
voidFreeNode(ListNode*node)
{
spin_mutex::scoped_locklock(ListMutex);
node->next = FreeList;
FreeList= node;
}
Конструктор scoped_lockожидает освобождения мьютексаListMutex
Структурный блок (операторные скобки {}) внутриAllocateNodeнужен для того, чтобы при выходе из него автоматически вызывался деструктор класса scoped_lock, который освобождает мьютекс
Программная идиома RAII –Resource Acquisition Is Initialization (получение ресурса есть инициализация)
46. spin_mutex
46
ListNode*AllocateNode()
{
ListNode*node;
spin_mutex::scoped_locklock;
lock.acquire(ListMutex);
node = FreeList;
if(node)
FreeList= node->next;
lock.release();
if(!node)
node = new ListNode();
returnnode;
}
Если защищенный блок (acquire-release) сгенерирует исключение, то release вызван не будет!
Используйте RAIIесли в пределах критической секции возможно возникновение исключительной ситуации
47. Атомарные операции (Atomic operations)
47
Атомарная операция(Atomic operation)–это операций, которая в любой момент времени выполняется только одним потоком
Атомарные операции намного “легче”мьютексов– не требуют блокирования потоков
TBB поддерживаем атомарные переменные
atomic<T> AtomicVariableName
48. Атомарные операции (Atomic operations)
48
Операции над переменной atomic<T> x
= x-чтение значения переменной x
x = -запись в переменную xзначения и его возврат
x.fetch_and_store(y) x= yи возврат старого значения x
x.fetch_and_add(y) x+= yи возврат старого значения x
x.compare_and_swap(y, z) если x= z, то x= y, возврат старого значения x
51. Аллокаторыпамяти
51
Intel TBB предоставляет два аллокаторапамяти (альтернативы STL std::allocator)
scalable_allocator<T>–обеспечивает параллельное выделение памяти нескольким потокам
cache_aligned_allocator<T>–обеспечивает выделение блоков памяти, выравненных на границудлины кеш- линии (cacheline)
Это позволяет избежать ситуации когда потоки на разных процессорах пытаются модифицировать разные слова памяти, попадающие в одну строку кэша, и как следствие, постоянно перезаписываемую из памяти в кеш
52. Аллокаторыпамяти
52
/* STL vector будет использовать аллокаторTBB */
std::vector<int, cache_aligned_allocator<int> > v;
53. Ссылки
53
James Reinders. Intel Threading Building Blocks. –O'Reilly, 2007. –336p.
Intel Threading Building Blocks Documentation// http://software.intel.com/sites/products/documentation/doclib/tbb_sa/help/index.htm