SlideShare a Scribd company logo
КурносовМихаил Георгиевич 
E-mail: mkurnosov@gmail.com 
WWW: www.mkurnosov.net 
Курс “Высокопроизводительные вычислительные системы” 
Сибирский государственный университет телекоммуникаций и информатики (Новосибирск) 
��сенний семестр, 2014 
Лекция 8Intel Threading Building Blocks
Intel Threading Building Blocks 
2 
Intel Treading Building Blocks (TBB) – это кроссплатформенная библиотека шаблонов C++ для создания многопоточных программ 
История развития: 
o2006 –Intel TBB v1.0 (Intel compiler only) 
o2007 –Intel TBB v2.0 (Open Source, GPLv2) 
o2008–Intel TBB v2.1(thread affinity, cancellation) 
o2009–Intel TBB v2.2(C++0x lambda functions) 
o… 
o2011–Intel TBB v4.0 
o2012 –Intel TBB v4.1 
o2014–Intel TBB v4.3 
http://threadingbuildingblocks.org
Intel Threading Building Blocks 
3 
Open Source Community Version GPL v2 
Поддерживаемые операционные системы: 
oMicrosoft Windows {XP, 7, Server 2008, …} 
oGNU/Linux + Android 
oApple Mac OSX 10.7.4, … 
http://threadingbuildingblocks.org
Состав Intel TBB 
4 
Алгоритмы:parallel_for,parallel_reduce, parallel_scan, parallel_while,parallel_do, parallel_pipeline,parallel_sort 
Контейнеры:concurrent_queue, concurrent_vector,concurrent_hash_map 
Аллокаторыпамяти:scalable_malloc,scalable_free, scalable_realloc,scalable_calloc, scalable_allocator,cache_aligned_allocator 
Мьютексы: mutex,spin_mutex,queuing_mutex, spin_rw_mutex,queuing_rw_mutex,recursive mutex 
Атомарные операции: fetch_and_add, fetch_and_increment,fetch_and_decrement, compare_and_swap,fetch_and_store 
Task-based parallelism (fork-join) + work stealing
Intel Threading Building Blocks 
5 
Intel TBB позволяет абстрагироваться от низкоуровневых потоков и распараллеливать программу в терминах параллельно выполняющихся задач(task parallelism) 
Задачи TBB “легче” потоков операционной системы 
Планировщик TBB использует механизм “work stealing” для распределения задач по потокам 
Все компоненты Intel TBB определены в пространстве имен C++ (namespace)“tbb”
Компиляция программ с Intel TBB 
6 
$ g++ –Wall –o prog./prog.cpp –ltbb 
C:> icl/MD prog.cpp tbb.lib 
GNU/Linux 
Microsoft Windows (Intel C++ Compiler)
// 
// tbb_hello.cpp: TBB Hello World 
// 
#include <cstdio> 
#include <tbb/tbb.h> 
// Function object 
classMyTask{ 
public: 
MyTask(constchar*name): name_(name) {} 
voidoperator()() const 
{ 
// Task code 
std::printf("Hello from task %sn", name_); 
} 
private: 
constchar *name_; 
}; 
Intel TBB: Hello World!
Intel TBB: Hello World! (продолжение) 
8 
intmain( ) 
{ 
tbb::task_grouptg; 
tg.run(MyTask("1")); // Spawn task 
tg.run(MyTask("2")); // Spawn task 
tg.wait(); // Wait tasks 
return0; 
}
Компиляция и запуск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 
Любой поток использующий алгоритмыили планировщик TBB должен иметь инициализированный объект tbb::task_scheduler_init 
TBB >= 2.2 автоматически инициализирует планировщик 
Явная инициализация планировщика позво��яет: 
управлять когда создается и уничтожается планировщик 
устанавливать количество используемых потоков выполнения 
устанавливать размер стека для потоков выполнения
Инициализация библиотеки 
11 
#include <tbb/task_scheduler_init.h> 
intmain() 
{ 
tbb::task_scheduler_initinit; 
return0; 
} 
Явная инициализация планировщика
Инициализация библиотеки 
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 
#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; 
}
Распараллеливание циклов 
14 
В TBB реализованы шаблоны параллельных алгоритмов 
parallel_for 
parallel_reduce 
parallel_scan 
parallel_do 
parallel_for_each 
parallel_pipeline 
parallel_sort 
parallel_invoke
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()содержит код обработки блока итераций
#include <iostream> 
#include <tbb/task_scheduler_init.h> 
#include <tbb/tick_count.h> 
#include <tbb/parallel_for.h> 
#include <tbb/blocked_range.h> 
classsaxpy_par{ 
public: 
saxpy_par(floata, float*x, float*y): 
a_(a), x_(x), y_(y) {} 
voidoperator()(constblocked_range<size_t> &r) const 
{ 
for(size_ti= r.begin(); i!= r.end(); ++i) 
y_[i] += a_ * x_[i]; 
} 
private: 
floatconsta_; 
float*constx_; 
float*consty_; 
}; 
parallel_for 
16
intmain() 
{ 
floata = 2.0; 
float*x, *y; 
size_tn = 100000000; 
x = newfloat[n]; 
y = newfloat[n]; 
for(size_ti= 0; i< n; ++i) 
x[i] = 5.0; 
tick_countt0 = tick_count::now(); 
task_scheduler_initinit(4); 
parallel_for(blocked_range<size_t>(0, n), saxpy_par(a, x, y), 
auto_partitioner()); 
tick_countt1 = tick_count::now(); 
cout<< "Time: "<< (t1 -t0).seconds() << " sec."<< endl; 
delete[] x; 
delete[] y; 
return0; 
} 
parallel_for 
17
intmain() 
{ 
floata = 2.0; 
float*x, *y; 
size_tn = 100000000; 
x = newfloat[n]; 
y = newfloat[n]; 
for(size_ti= 0; i< n; ++i) 
x[i] = 5.0; 
tick_countt0 = tick_count::now(); 
task_scheduler_initinit(4); 
parallel_for(blocked_range<size_t>(0, n), saxpy_par(a, x, y), 
auto_partitioner()); 
tick_countt1 = tick_count::now(); 
cout<< "Time: "<< (t1 -t0).seconds() << " sec."<< endl; 
delete[] x; 
delete[] y; 
return0; 
} 
parallel_for 
18 
Классblocked_range(begin, end, grainsize) описывает одномерное пространство итераций 
В Intel TBB доступно описание многомерных пространств итераций (blocked_range2d, ...)
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; 
}
intmain() 
{ 
// ... 
x = newfloat[n]; 
y = newfloat[n]; 
for(size_ti= 0; i< n; ++i) 
x[i] = 5.0; 
tick_countt0 = tick_count::now(); 
parallel_for(blocked_range<size_t>(0, n), 
[=](constblocked_range<size_t>& r) { 
for (size_ti= r.begin(); i!= r.end(); ++i) 
y[i] += a * x[i]; 
} 
); 
tick_countt1 = tick_count::now(); 
cout<< "Time: "<< (t1 -t0).seconds() << " sec."<< endl; 
delete[] x; 
delete[] y; 
return0; 
} 
parallel_for(C++11 lambda expressions) 
20
intmain() 
{ 
// ... 
x = newfloat[n]; 
y = newfloat[n]; 
for(size_ti= 0; i< n; ++i) 
x[i] = 5.0; 
tick_countt0 = tick_count::now(); 
parallel_for(blocked_range<size_t>(0, n), 
[=](constblocked_range<size_t>& r) { 
for (size_ti= r.begin(); i!= r.end(); ++i) 
y[i] += a * x[i]; 
} 
); 
tick_countt1 = tick_count::now(); 
cout<< "Time: "<< (t1 -t0).seconds() << " sec."<< endl; 
delete[] x; 
delete[] y; 
return0; 
} 
parallel_for(C++11 lambda expressions) 
21 
Анонимная функция (лямбда-функция, С++11) 
[=]–захватить все автоматические переменные 
(constblocked_range…) –аргументы функции 
{ ... } –код функции
parallel_reduce 
22 
floatreduce(float*x, size_tn) 
{ 
floatsum = 0.0; 
for(size_ti= 0; i< n; ++i) 
sum += x[i]; 
returnsum; 
} 
parallel_reduceпозволяет распараллеливать циклы и выполнять операцию редукции
classreduce_par{ 
public: 
floatsum; 
void operator()(constblocked_range<size_t> &r) 
{ 
floatsum_local= sum; 
float*xloc= x_; 
size_tend = r.end(); 
for(size_ti= r.begin(); i!= end; ++i) 
sum_local+= xloc[i]; 
sum = sum_local; 
} 
// Splitting constructor: вызывается при порождении новой задачи 
reduce_par(reduce_par& r, split): sum(0.0), x_(r.x_) {} 
// Join: объединяет результаты двух задач (текущей и r) 
voidjoin(constreduce_par& r) {sum += r.sum;} 
reduce_par(float*x): sum(0.0), x_(x) {} 
private: 
float*x_; 
}; 
parallel_reduce 
23
intmain() 
{ 
size_tn = 10000000; 
float*x = newfloat[n]; 
for(size_ti= 0; i< n; ++i) 
x[i] = 1.0; 
tick_countt0 = tick_count::now(); 
reduce_parr(x); 
parallel_reduce(blocked_range<size_t>(0, n), r); 
tick_countt1 = tick_count::now(); 
cout<< "Reduce: " << std::fixed << r.sum<< "n"; 
cout<< "Time: " << (t1 -t0).seconds() << " sec." << endl; 
delete[] x; 
return0; 
} 
parallel_reduce 
24
parallel_sort 
25 
voidparallel_sort(RandomAccessIteratorbegin, 
RandomAccessIteratorend, 
constCompare& comp); 
parallel_sortпозволяет упорядочивать последовательностиэлементов 
Применяется детерминированный алгоритм нестабильной сортировки с трудоемкостью O(nlogn)–алгоритм не гарантирует сохранения порядка следования элементов с одинаковыми ключами
parallel_sort 
26 
#include <cstdlib> 
#include <tbb/parallel_sort.h> 
using namespace std; 
using namespace tbb; 
intmain() 
{ 
size_tn = 10; 
float*x = newfloat[n]; 
for(size_ti= 0; i< n; ++i) 
x[i] = static_cast<float>(rand()) / RAND_MAX * 100; 
parallel_sort(x, x + n, std::greater<float>()); 
delete[] x; 
return 0; 
}
Планировщик задач (Task scheduler) 
27 
Intel TBB позволяет абстрагироваться от реальных потоков операционной системы и разрабатывать программу в терминах параллельных задач (task-based parallel programming) 
Запуск TBB-задачи примерно в 18 раз быстрее запуска потока POSIXв GNU/Linux (в Microsoft Windows примерно в 100 разбыстрее) 
В отличии от планировщика POSIX-потоков в GNU/Linux планировщик TBBреализует “не справедливую” (unfair) политику распределения задач по потокам
Числа Фибоначчи: sequential version 
28 
intfib(intn) 
{ 
if(n < 2) 
returnn; 
returnfib(n -1) + fib(n -2); 
}
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запускает задачу на выполнение и ожидает её завершения
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–запускает задачу и ожидает завершения всех дочерних задач
intmain() 
{ 
intn = 42; 
tick_countt0 = tick_count::now(); 
intf = fib_par(n); 
tick_countt1 = tick_count::now(); 
cout<< "Fib = " << f << endl; 
cout<< "Time: " << std::fixed << (t1 -t0).seconds() 
<< " sec." << endl; 
return0; 
} 
Числа Фибоначчи: parallel version 
31
Граф задачи (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
Планирование задач (Task scheduling) 
33 
Каждый поток поддерживает дек готовых к выполнению задач (deque, двусторонняя очередь) 
Планировщик использует комбинированный алгоритма на основе обход графа задач в ширину и глубину 
Task E 
Task D 
Top: Oldest task 
Bottom: Youngest Task
Планирование задач (Task scheduling) 
34 
Листовые узлы в графе задач –это задачи готовые к выполнению (ready task, они не ожидают других) 
Потоки могу захватывать (steal) задачи из чужих деков (с их верхнего конца) 
Task E 
Task D 
Top: Oldest task 
Bottom: Youngest Task 
Top: Oldest task 
Bottom: Youngest Task
Выбор задачи из дека 
35 
Задача для выполнения выбирается одним из следующих способов (в порядке уменьшения приоритета): 
1.Выбирается задача, на которую возвращен указатель методом executeпредыдущей задачи 
2.Выбирается задача с нижнего конца (bottom) дека потока 
3.Выбирается первая задача из дека (с его верхнего конца) случайно выбранного потока–work stealing
Помещение задачи в дек потока 
36 
Задачи помещаются в дек с его нижнего конца 
В дек помещается задача порожденная методомspawn 
Задача может быть направлена на повторное выполнение методом task::recycle_to_reexecute 
Задача имеет счетчик ссылок (reference count) равный нулю –все дочерние задачи завершены
Потокобезопасныеконтейнеры 
37 
Intel TBB предоставляет классы контейнеров (concurrent containers), которые корректно могут обновляться из нескольких потоков 
Для работы в многопоточной программе со стандартными контейнерами STL доступ к ним необходимо защищать блокировками (мьютексами) 
Особенности Intel TBB: 
oпри работе с контейнерами применяет алгоритмы не требующие блокировок (lock-free algorithms) 
oпри необходимости блокируются лишь небольшие участки кода контейнеров (fine-grained locking)
Потокобезопасныеконтейнеры 
38 
concurrent_hash_map 
concurrent_vector 
concurrent_queue
concurrent_vector 
39 
voidappend(concurrent_vector<char> &vec, constchar*str) 
{ 
size_tn = strlen(str) + 1; 
std::copy(str, str+ n, 
vec.begin() + vec.grow_by(n)); 
} 
Метод grow_by(n)безопасно добавляет nэлементов к векторуconcurrent_vector
Взаимные исключения (Mutual exclusion) 
40 
Взаимные исключения (mutual exclusion)позволяют управлять количеством потоков, одновременно выполняющих заданный участок кода 
В Intel TBB взаимные исключения реализованы средствами мьютексов(mutexes) и блокировок (locks) 
Мьютекс(mutex)–это объект синхронизации, который в любой момент времени может быть захвачен только одним потоком, остальные потоки ожидают его освобождения
Свойства мьютексовIntel TBB 
41 
Scalable 
Fair–справедливые мьютексызахватываются в порядке обращения к ним потоков (даже если следующий поток в очереди находится в состоянии сна; несправедливыемьютексымогут быть быстрее) 
Recursive–рекурсивные мьютексыпозволяют потоку захватившему мьютексповторно его получить 
Yield–при длительном ожидании мьютексапоток периодически проверяет его текущее состояние и снимает себя с процессора (засыпает, в GNU/Linux вызывается sched_yield(),а в Microsoft Windows –SwitchToThread()) 
Block–потока освобождает процессор до тех пор, пока не освободится мьютекс(такие мьютексырекомендуется использовать при длительных ожиданиях)
Мьютексы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)
Мьютексы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
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; 
}
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 (получение ресурса есть инициализация)
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если в пределах критической секции возможно возникновение исключительной ситуации
Атомарные операции (Atomic operations) 
47 
Атомарная операция(Atomic operation)–это операций, которая �� любой момент времени выполняется только одним потоком 
Атомарные операции намного “легче”мьютексов– не требуют блокирования потоков 
TBB поддерживаем атомарные переменные 
atomic<T> AtomicVariableName
Атомарные операции (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
Атомарные операции (Atomic operations) 
49 
atomic<int> counter; 
unsigned intGetUniqueInteger() 
{ 
returncounter.fetch_and_add(1); 
}
Атомарные операции (Atomic operations) 
50 
atomic<int> Val; 
intUpdateValue() 
{ 
do{ 
v = Val; 
newv= f(v); 
} while(Val.compare_and_swap(newv, v) != v); 
returnv; 
}
Аллокаторыпамяти 
51 
Intel TBB предоставляет два аллокаторапамяти (альтернативы STL std::allocator) 
scalable_allocator<T>–обеспечивает параллельное выделение памяти нескольким потокам 
cache_aligned_allocator<T>–обеспечивает выделение блоков памяти, выравненных на границудлины кеш- линии (cacheline) 
Это позволяет избежать ситуации когда потоки на разных процессорах пытаются модифицировать разные слова памяти, попадающие в одну строку кэша, и как следствие, постоянно перезаписываемую из памяти в кеш
Аллокаторыпамяти 
52 
/* STL vector будет использовать аллокаторTBB */ 
std::vector<int, cache_aligned_allocator<int> > v;
Ссылки 
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

More Related Content

Лекция 8. Intel Threading Building Blocks

  • 1. КурносовМихаил Георгиевич E-mail: mkurnosov@gmail.com WWW: www.mkurnosov.net Курс “Высокопроизводительные вычислительные системы” Сибирский государственный университет телекоммуникаций и информатики (Новосибирск) Осенний семестр, 2014 Лекция 8Intel Threading Building Blocks
  • 2. Intel Threading Building Blocks 2 Intel Treading Building Blocks (TBB) – это кроссплатформенная библиотека шаблонов C++ для создания многопоточных программ История развития: o2006 –Intel TBB v1.0 (Intel compiler only) o2007 –Intel TBB v2.0 (Open Source, GPLv2) o2008–Intel TBB v2.1(thread affinity, cancellation) o2009–Intel TBB v2.2(C++0x lambda functions) o… o2011–Intel TBB v4.0 o2012 –Intel TBB v4.1 o2014–Intel TBB v4.3 http://threadingbuildingblocks.org
  • 3. Intel Threading Building Blocks 3 Open Source Community Version GPL v2 Поддерживаемые операционные системы: oMicrosoft Windows {XP, 7, Server 2008, …} oGNU/Linux + Android oApple Mac OSX 10.7.4, … http://threadingbuildingblocks.org
  • 4. Состав Intel TBB 4 Алгоритмы:parallel_for,parallel_reduce, parallel_scan, parallel_while,parallel_do, parallel_pipeline,parallel_sort Контейнеры:concurrent_queue, concurrent_vector,concurrent_hash_map Аллокаторыпамяти:scalable_malloc,scalable_free, scalable_realloc,scalable_calloc, scalable_allocator,cache_aligned_allocator Мьютексы: mutex,spin_mutex,queuing_mutex, spin_rw_mutex,queuing_rw_mutex,recursive mutex Атомарные операции: fetch_and_add, fetch_and_increment,fetch_and_decrement, compare_and_swap,fetch_and_store Task-based parallelism (fork-join) + work stealing
  • 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)
  • 7. // // tbb_hello.cpp: TBB Hello World // #include <cstdio> #include <tbb/tbb.h> // Function object classMyTask{ public: MyTask(constchar*name): name_(name) {} voidoperator()() const { // Task code std::printf("Hello from task %sn", name_); } private: constchar *name_; }; Intel TBB: Hello World!
  • 8. Intel TBB: Hello World! (продолжение) 8 intmain( ) { tbb::task_grouptg; tg.run(MyTask("1")); // Spawn task tg.run(MyTask("2")); // Spawn task tg.wait(); // Wait tasks return0; }
  • 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 автоматически инициализирует планировщик Явная инициализация планировщика позволяет: управлять когда создается и уничтожается планировщик устанавливать количество используемых потоков выполнения устанавливать размер стека для потоков выполнения
  • 11. Инициализация библиотеки 11 #include <tbb/task_scheduler_init.h> intmain() { tbb::task_scheduler_initinit; return0; } Явная инициализация планировщика
  • 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; }
  • 14. Распараллеливание циклов 14 В TBB реализованы шаблоны параллельных алгоритмов parallel_for parallel_reduce parallel_scan parallel_do parallel_for_each parallel_pipeline parallel_sort parallel_invoke
  • 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()содержит код обработки блока итераций
  • 16. #include <iostream> #include <tbb/task_scheduler_init.h> #include <tbb/tick_count.h> #include <tbb/parallel_for.h> #include <tbb/blocked_range.h> classsaxpy_par{ public: saxpy_par(floata, float*x, float*y): a_(a), x_(x), y_(y) {} voidoperator()(constblocked_range<size_t> &r) const { for(size_ti= r.begin(); i!= r.end(); ++i) y_[i] += a_ * x_[i]; } private: floatconsta_; float*constx_; float*consty_; }; parallel_for 16
  • 17. intmain() { floata = 2.0; float*x, *y; size_tn = 100000000; x = newfloat[n]; y = newfloat[n]; for(size_ti= 0; i< n; ++i) x[i] = 5.0; tick_countt0 = tick_count::now(); task_scheduler_initinit(4); parallel_for(blocked_range<size_t>(0, n), saxpy_par(a, x, y), auto_partitioner()); tick_countt1 = tick_count::now(); cout<< "Time: "<< (t1 -t0).seconds() << " sec."<< endl; delete[] x; delete[] y; return0; } parallel_for 17
  • 18. intmain() { floata = 2.0; float*x, *y; size_tn = 100000000; x = newfloat[n]; y = newfloat[n]; for(size_ti= 0; i< n; ++i) x[i] = 5.0; tick_countt0 = tick_count::now(); task_scheduler_initinit(4); parallel_for(blocked_range<size_t>(0, n), saxpy_par(a, x, y), auto_partitioner()); tick_countt1 = tick_count::now(); cout<< "Time: "<< (t1 -t0).seconds() << " sec."<< endl; delete[] x; delete[] y; return0; } parallel_for 18 Классblocked_range(begin, end, grainsize) описывает одномерное пространство итераций В Intel TBB доступно описание многомерных пространств итераций (blocked_range2d, ...)
  • 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; }
  • 20. intmain() { // ... x = newfloat[n]; y = newfloat[n]; for(size_ti= 0; i< n; ++i) x[i] = 5.0; tick_countt0 = tick_count::now(); parallel_for(blocked_range<size_t>(0, n), [=](constblocked_range<size_t>& r) { for (size_ti= r.begin(); i!= r.end(); ++i) y[i] += a * x[i]; } ); tick_countt1 = tick_count::now(); cout<< "Time: "<< (t1 -t0).seconds() << " sec."<< endl; delete[] x; delete[] y; return0; } parallel_for(C++11 lambda expressions) 20
  • 21. intmain() { // ... x = newfloat[n]; y = newfloat[n]; for(size_ti= 0; i< n; ++i) x[i] = 5.0; tick_countt0 = tick_count::now(); parallel_for(blocked_range<size_t>(0, n), [=](constblocked_range<size_t>& r) { for (size_ti= r.begin(); i!= r.end(); ++i) y[i] += a * x[i]; } ); tick_countt1 = tick_count::now(); cout<< "Time: "<< (t1 -t0).seconds() << " sec."<< endl; delete[] x; delete[] y; return0; } parallel_for(C++11 lambda expressions) 21 Анонимная функция (лямбда-функция, С++11) [=]–захватить все автоматические переменные (constblocked_range…) –аргументы функции { ... } –код функции
  • 22. parallel_reduce 22 floatreduce(float*x, size_tn) { floatsum = 0.0; for(size_ti= 0; i< n; ++i) sum += x[i]; returnsum; } parallel_reduceпозволяет распараллеливать циклы и выполнять операцию редукции
  • 23. classreduce_par{ public: floatsum; void operator()(constblocked_range<size_t> &r) { floatsum_local= sum; float*xloc= x_; size_tend = r.end(); for(size_ti= r.begin(); i!= end; ++i) sum_local+= xloc[i]; sum = sum_local; } // Splitting constructor: вызывается при порождении новой задачи reduce_par(reduce_par& r, split): sum(0.0), x_(r.x_) {} // Join: объединяет результаты двух задач (текущей и r) voidjoin(constreduce_par& r) {sum += r.sum;} reduce_par(float*x): sum(0.0), x_(x) {} private: float*x_; }; parallel_reduce 23
  • 24. intmain() { size_tn = 10000000; float*x = newfloat[n]; for(size_ti= 0; i< n; ++i) x[i] = 1.0; tick_countt0 = tick_count::now(); reduce_parr(x); parallel_reduce(blocked_range<size_t>(0, n), r); tick_countt1 = tick_count::now(); cout<< "Reduce: " << std::fixed << r.sum<< "n"; cout<< "Time: " << (t1 -t0).seconds() << " sec." << endl; delete[] x; return0; } parallel_reduce 24
  • 25. parallel_sort 25 voidparallel_sort(RandomAccessIteratorbegin, RandomAccessIteratorend, constCompare& comp); parallel_sortпозволяет упорядочивать последовательностиэлементов Применяется детерминированный алгоритм нестабильной сортировки с трудоемкостью O(nlogn)–алгоритм не гарантирует сохранения порядка следования элементов с одинаковыми ключами
  • 26. parallel_sort 26 #include <cstdlib> #include <tbb/parallel_sort.h> using namespace std; using namespace tbb; intmain() { size_tn = 10; float*x = newfloat[n]; for(size_ti= 0; i< n; ++i) x[i] = static_cast<float>(rand()) / RAND_MAX * 100; parallel_sort(x, x + n, std::greater<float>()); delete[] x; return 0; }
  • 27. Планировщик задач (Task scheduler) 27 Intel TBB позволяет абстрагироваться от реальных потоков операционной системы и разрабатывать программу в терминах параллельных задач (task-based parallel programming) Запуск TBB-задачи примерно в 18 раз быстрее запуска потока POSIXв GNU/Linux (в Microsoft Windows примерно в 100 разбыстрее) В отличии от планировщика POSIX-потоков в GNU/Linux планировщик TBBреализует “не справедливую” (unfair) политику распределения задач по потокам
  • 28. Числа Фибоначчи: sequential version 28 intfib(intn) { if(n < 2) returnn; returnfib(n -1) + fib(n -2); }
  • 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–запускает задачу и ожидает завершения всех дочерних задач
  • 31. intmain() { intn = 42; tick_countt0 = tick_count::now(); intf = fib_par(n); tick_countt1 = tick_count::now(); cout<< "Fib = " << f << endl; cout<< "Time: " << std::fixed << (t1 -t0).seconds() << " sec." << endl; return0; } Числа Фибоначчи: parallel version 31
  • 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)
  • 39. concurrent_vector 39 voidappend(concurrent_vector<char> &vec, constchar*str) { size_tn = strlen(str) + 1; std::copy(str, str+ n, vec.begin() + vec.grow_by(n)); } Метод grow_by(n)безопасно добавляет nэлементов к векторуconcurrent_vector
  • 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
  • 49. Атомарные операции (Atomic operations) 49 atomic<int> counter; unsigned intGetUniqueInteger() { returncounter.fetch_and_add(1); }
  • 50. Атомарные операции (Atomic operations) 50 atomic<int> Val; intUpdateValue() { do{ v = Val; newv= f(v); } while(Val.compare_and_swap(newv, v) != v); returnv; }
  • 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