+ All Categories
Home > Software > Лекция 8. Intel Threading Building Blocks

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

Date post: 02-Jul-2015
Category:
Upload: mikhail-kurnosov
View: 291 times
Download: 1 times
Share this document with a friend
Description:
Intel Threading Building Blocks
53
Курносов Михаил Георгиевич E-mail: [email protected] WWW: www.mkurnosov.net Курс “Высокопроизводительные вычислительные системы” Сибирский государственный университет телекоммуникаций и информатики (Новосибирск) Осенний семестр, 2014 Лекция 8 Intel Threading Building Blocks
Transcript
Page 1: Лекция 8. Intel Threading Building Blocks

Курносов Михаил Георгиевич

E-mail: [email protected]: www.mkurnosov.net

Курс “Высокопроизводительные вычислительные системы”Сибирский государственный университет телекоммуникаций и информатики (Новосибирск)Осенний семестр, 2014

Лекция 8Intel Threading Building Blocks

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

Intel Threading Building Blocks

22

Intel Treading Building Blocks (TBB) –

это кроссплатформенная библиотека шаблонов

C++ для создания многопоточных программ

История развития:

o 2006 – Intel TBB v1.0 (Intel compiler only)

o 2007 – Intel TBB v2.0 (Open Source, GPLv2)

o 2008 – Intel TBB v2.1 (thread affinity, cancellation)

o 2009 – Intel TBB v2.2 (C++0x lambda functions)

o …

o 2011 – Intel TBB v4.0

o 2012 – Intel TBB v4.1

o 2014 – Intel TBB v4.3

http://threadingbuildingblocks.org

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

Intel Threading Building Blocks

33

Open Source Community Version GPL v2

Поддерживаемые операционные системы:

o Microsoft Windows {XP, 7, Server 2008, …}

o GNU/Linux + Android

o Apple Mac OS X 10.7.4, …

http://threadingbuildingblocks.org

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

Состав Intel TBB

44

Алгоритмы: 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

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

Intel Threading Building Blocks

55

Intel TBB позволяет абстрагироваться от низкоуровневых

потоков и распараллеливать программу в терминах

параллельно выполняющихся задач (task parallelism)

Задачи TBB “легче” потоков операционной системы

Планировщик TBB использует механизм “work stealing”

для распределения задач по потокам

Все компоненты Intel TBB определены в пространстве

имен C++ (namespace) “tbb”

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

Компиляция программ с Intel TBB

66

$ g++ –Wall –o prog ./prog.cpp –ltbb

C:\> icl /MD prog.cpp tbb.lib

GNU/Linux

Microsoft Windows (Intel C++ Compiler)

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

// // tbb_hello.cpp: TBB Hello World//#include <cstdio>#include <tbb/tbb.h>

// Function object class MyTask {public:

MyTask(const char *name): name_(name) {}

void operator()() const{

// Task codestd::printf("Hello from task %s\n", name_);

}

private:const char *name_;

};

Intel TBB: Hello World!

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

Intel TBB: Hello World! (продолжение)

88

int main( ){

tbb::task_group tg;

tg.run(MyTask("1")); // Spawn tasktg.run(MyTask("2")); // Spawn tasktg.wait(); // Wait tasks

return 0;}

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

Компиляция и запуск tbb_hello

99

$ 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

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

Инициализация библиотеки

1010

Любой поток использующий алгоритмы или планировщик TBB должен иметь инициализированный объект tbb::task_scheduler_init

TBB >= 2.2 автоматически инициализирует планировщик

Явная инициализация планировщика позволяет:

управлять когда создается и уничтожается планировщик

устанавливать количество используемых потоков выполнения

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

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

Инициализация библиотеки

1111

#include <tbb/task_scheduler_init.h>

int main() {

tbb::task_scheduler_init init;

return 0;}

Явная инициализация планировщика

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

Инициализация библиотеки

1212

Конструктор класса task_scheduler_init принимает

два параметра:

task_scheduler_init(int max_threads = automatic,

stack_size_type thread_stack_size = 0);

Допустимые значения параметра max_threads:

task_scheduler_init::automatic –количество потоков определяется автоматически

task_scheduler_init::deferred –инициализация откладывается до явного вызова метода task_scheduler_init::initialize(max_threads)

Положительное целое – количество потоков

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

Инициализация библиотеки

1313

#include <iostream>

#include <tbb/task_scheduler_init.h>

int main()

{

int n = tbb::task_scheduler_init::default_num_threads();

for (int p = 1; p <= n; ++p) {

// Construct task scheduler with p threads

tbb::task_scheduler_init init(p);

std::cout << "Is active = " << init.is_active()

<< std::endl;

}

return 0;

}

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

Распараллеливание циклов

1414

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

parallel_for

parallel_reduce

parallel_scan

parallel_do

parallel_for_each

parallel_pipeline

parallel_sort

parallel_invoke

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

parallel_for

1515

void saxpy(float a, float *x, float *y, size_t n)

{

for (size_t i = 0; i < n; ++i)

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

}

parallel_for позволяет разбить пространство итерации

на блоки (chunks), которые обрабатываются разными

потоками

Требуется создать класс, в котором перегруженный

оператор вызова функции operator() содержит код

обработки блока итераций

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

#include <iostream>#include <tbb/task_scheduler_init.h>#include <tbb/tick_count.h>#include <tbb/parallel_for.h>#include <tbb/blocked_range.h>

class saxpy_par {public:

saxpy_par(float a, float *x, float *y):a_(a), x_(x), y_(y) {}

void operator()(const blocked_range<size_t> &r) const{

for (size_t i = r.begin(); i != r.end(); ++i) y_[i] += a_ * x_[i];

}

private:float const a_;float *const x_;float *const y_;

};

parallel_for

1616

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

int main() {

float a = 2.0;float *x, *y;size_t n = 100000000;

x = new float[n];y = new float[n];for (size_t i = 0; i < n; ++i)

x[i] = 5.0;

tick_count t0 = tick_count::now();task_scheduler_init init(4);parallel_for(blocked_range<size_t>(0, n), saxpy_par(a, x, y),

auto_partitioner());tick_count t1 = tick_count::now();cout << "Time: " << (t1 - t0).seconds() << " sec." << endl;

delete[] x;delete[] y;

return 0;}

parallel_for

1717

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

int main() {

float a = 2.0;float *x, *y;size_t n = 100000000;

x = new float[n];y = new float[n];for (size_t i = 0; i < n; ++i)

x[i] = 5.0;

tick_count t0 = tick_count::now();task_scheduler_init init(4);parallel_for(blocked_range<size_t>(0, n), saxpy_par(a, x, y),

auto_partitioner());tick_count t1 = tick_count::now();cout << "Time: " << (t1 - t0).seconds() << " sec." << endl;

delete[] x;delete[] y;

return 0;}

parallel_for

1818

Класс blocked_range(begin, end, grainsize) описывает одномерное

пространство итераций

В Intel TBB доступно описание многомерных пространств итераций

(blocked_range2d, ...)

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

affinity_partitioner

1919

Класс affinity_partitioner запоминает какими потоками выполнялись предыдущие итерации и пытается распределять блоки итераций с учетом этой информации – последовательные блоки назначаются на один и тот же поток для эффективного использования кеш-памяти

int main(){

// ... static affinity_partitioner ap;parallel_for(blocked_range<size_t>(0, n),

saxpy_par(a, x, y), ap);// ...return 0;

}

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

int main() {

// ...x = new float[n];y = new float[n];for (size_t i = 0; i < n; ++i)

x[i] = 5.0;

tick_count t0 = tick_count::now();parallel_for(blocked_range<size_t>(0, n),

[=](const blocked_range<size_t>& r) {for (size_t i = r.begin(); i != r.end(); ++i)

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

});

tick_count t1 = tick_count::now();cout << "Time: " << (t1 - t0).seconds() << " sec." << endl;

delete[] x;delete[] y; return 0;

}

parallel_for (C++11 lambda expressions)

2020

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

int main() {

// ...x = new float[n];y = new float[n];for (size_t i = 0; i < n; ++i)

x[i] = 5.0;

tick_count t0 = tick_count::now();parallel_for(blocked_range<size_t>(0, n),

[=](const blocked_range<size_t>& r) {for (size_t i = r.begin(); i != r.end(); ++i)

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

});

tick_count t1 = tick_count::now();cout << "Time: " << (t1 - t0).seconds() << " sec." << endl;

delete[] x;delete[] y; return 0;

}

parallel_for (C++11 lambda expressions)

2121

Анонимная функция (лямбда-функция, С++11) [=] – захватить все автоматические переменные (const blocked_range …) – аргументы функции { ... } – код функции

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

parallel_reduce

2222

float reduce(float *x, size_t n){

float sum = 0.0;for (size_t i = 0; i < n; ++i)

sum += x[i];return sum;

}

parallel_reduce позволяет распараллеливать циклы

и выполнять операцию редукции

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

class reduce_par {public:

float sum;

void operator()(const blocked_range<size_t> &r){

float sum_local = sum;float *xloc = x_;size_t end = r.end();for (size_t i = 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)void join(const reduce_par& r) {sum += r.sum;}

reduce_par(float *x): sum(0.0), x_(x) {}

private:float *x_;

};

parallel_reduce

2323

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

int main()

{

size_t n = 10000000;

float *x = new float[n];

for (size_t i = 0; i < n; ++i)

x[i] = 1.0;

tick_count t0 = tick_count::now();

reduce_par r(x);

parallel_reduce(blocked_range<size_t>(0, n), r);

tick_count t1 = tick_count::now();

cout << "Reduce: " << std::fixed << r.sum << "\n";

cout << "Time: " << (t1 - t0).seconds() << " sec." << endl;

delete[] x;

return 0;

}

parallel_reduce

2424

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

parallel_sort

2525

void parallel_sort(RandomAccessIterator begin,

RandomAccessIterator end,

const Compare& comp);

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

элементов

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

нестабильной сортировки с трудоемкостью O(nlogn) – алгоритм

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

одинаковыми ключами

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

parallel_sort

2626

#include <cstdlib>#include <tbb/parallel_sort.h>

using namespace std;using namespace tbb;

int main() {

size_t n = 10;float *x = new float[n];for (size_t i = 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;

}

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

Планировщик задач (Task scheduler)

2727

Intel TBB позволяет абстрагироваться от реальных потоков

операционной системы и разрабатывать программу в

терминах параллельных задач (task-based parallel

programming)

Запуск TBB-задачи примерно в 18 раз быстрее запуска

потока POSIX в GNU/Linux (в Microsoft Windows примерно

в 100 раз быстрее)

В отличии от планировщика POSIX-потоков в GNU/Linux

планировщик TBB реализует “не справедливую” (unfair)

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

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

Числа Фибоначчи: sequential version

2828

int fib(int n){

if (n < 2)return n;

return fib(n - 1) + fib(n - 2); }

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

int fib_par(int n){

int val;

fibtask& t = *new(task::allocate_root()) fibtask(n, &val);task::spawn_root_and_wait(t);

return val;}

Числа Фибоначчи: parallel version

2929

allocate_root выделяет память под корневую задачу (task) класса fibtask

spawn_root_and_wait запускает задачу на выполнение и ожидает её

завершения

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

class fibtask: public task {public:

const int n;int* const val;

fibtask(int n_, int* val_): n(n_), val(val_) {}

task* execute() {

if (n < 10) {*val = fib(n); // Use sequential version

} else {int x, 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 waitset_ref_count(3);spawn(b);spawn_and_wait_for_all(a);*val = x + y;

}return NULL;

}};

Числа Фибоначчи: parallel version

3030

spawn запускает задачу на выполнение и не ожидает её завершения

spawn_and_wait_for_all – запускает задачу и ожидает завершения всех дочерних задач

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

int main() {

int n = 42;

tick_count t0 = tick_count::now();int f = fib_par(n);tick_count t1 = tick_count::now();

cout << "Fib = " << f << endl;cout << "Time: " << std::fixed << (t1 - t0).seconds()

<< " sec." << endl;return 0;

}

Числа Фибоначчи: parallel version

3131

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

Граф задачи (Task graph)

3232

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

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

Планирование задач (Task scheduling)

3333

Каждый поток поддерживает дек готовых к выполнению

задач (deque, двусторонняя очередь)

Планировщик использует комбинированный алгоритма

на основе обход графа задач в ширину и глубину

Task E

Task D

Top:Oldest

task

Bottom: Youngest

Task

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

Планирование задач (Task scheduling)

3434

Листовые узлы в графе задач – это задачи готовые

к выполнению (ready task, они не ожидают других)

Потоки могу захватывать (steal) задачи из чужих деков

(с их верхнего конца)

Task E

Task D

Top:Oldest

task

Bottom: Youngest

Task

Top:Oldest

task

Bottom: Youngest

Task

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

Выбор задачи из дека

3535

Задача для выполнения выбирается одним из следующих

способов (в порядке уменьшения приоритета):

1. Выбирается задача, на которую возвращен указатель

методом execute предыдущей задачи

2. Выбирается задача с нижнего конца (bottom) дека потока

3. Выбирается первая задача из дека (с его верхнего конца)

случайно выбранного потока – work stealing

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

Помещение задачи в дек потока

3636

Задачи помещаются в дек с его нижнего конца

В дек помещается задача порожденная методом spawn

Задача может быть направлена на повторное

выполнение методом

task::recycle_to_reexecute

Задача имеет счетчик ссылок (reference count)

равный нулю – все дочерние задачи завершены

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

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

3737

Intel TBB предоставляет классы контейнеров

(concurrent containers), которые корректно могут

обновляться из нескольких потоков

Для работы в многопоточной программе со

стандартными контейнерами STL доступ к ним

необходимо защищать блокировками (мьютексами)

Особенности Intel TBB:

o при работе с контейнерами применяет алгоритмы

не требующие блокировок (lock-free algorithms)

o при необходимости блокируются лишь небольшие

участки кода контейнеров (fine-grained locking)

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

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

3838

concurrent_hash_map

concurrent_vector

concurrent_queue

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

concurrent_vector

3939

void append(concurrent_vector<char> &vec, const char *str)

{size_t n = strlen(str) + 1;std::copy(str, str + n,

vec.begin() + vec.grow_by(n));}

Метод grow_by(n) безопасно добавляет n элементов

к вектору concurrent_vector

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

Взаимные исключения (Mutual exclusion)

4040

Взаимные исключения (mutual exclusion) позволяют

управлять количеством потоков, одновременно

выполняющих заданный участок кода

В Intel TBB взаимные исключения реализованы

средствами мьютексов (mutexes) и блокировок (locks)

Мьютекс (mutex) – это объект синхронизации,

который в любой момент времени может быть захвачен

только одним потоком, остальные потоки ожидают его

освобождения

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

Свойства мьютексов Intel TBB

4141

Scalable

Fair – справедливые мьютексы захватываются в порядке обращения к ним потоков (даже если следующий поток в очереди находится в состоянии сна; несправедливыемьютексы могут быть быстрее)

Recursive – рекурсивные мьютексы позволяют потоку захватившему мьютекс повторно его получить

Yield – при длительном ожидании мьютекса поток периодически проверяет его текущее состояние и снимает себя с процессора (засыпает, в GNU/Linux вызывается sched_yield(), а в Microsoft Windows – SwitchToThread())

Block – потока освобождает процессор до тех пор, пока не освободится мьютекс (такие мьютексы рекомендуется использовать при длительных ожиданиях)

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

Мьютексы Intel TBB

4242

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)

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

Мьютексы Intel TBB

4343

Mutex Scalable Fair RecursiveLongWait

Size

mutex OS dep. OS dep. No Blocks>= 3

words

recursive_mutex OS dep. OS dep. 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

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

spin_mutex

44

ListNode *FreeList;spin_mutex ListMutex;

ListNode *AllocateNode(){

ListNode *node;{

// Создать и захватить мьютекс (RAII)spin_mutex::scoped_lock lock(ListMutex);node = FreeList;if (node)

FreeList = node->next;} // Мьютекс автоматически освобождается if (!node)

node = new ListNode()return node;

}

44

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

spin_mutex

4545

void FreeNode(ListNode *node) {

spin_mutex::scoped_lock lock(ListMutex);node->next = FreeList;FreeList = node;

}

Конструктор scoped_lock ожидает освобождения мьютекса ListMutex

Структурный блок (операторные скобки {}) внутри AllocateNode

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

деструктор класса scoped_lock, который освобождает мьютекс

Программная идиома RAII – Resource Acquisition Is Initialization

(получение ресурса есть инициализация)

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

spin_mutex

4646

ListNode *AllocateNode() {

ListNode *node;spin_mutex::scoped_lock lock;lock.acquire(ListMutex);node = FreeList;if (node)

FreeList = node->next;lock.release();if (!node)

node = new ListNode();return node;

}

Если защищенный блок (acquire-release) сгенерирует исключение, то release вызван не будет!

Используйте RAII если в пределах критической секции возможно возникновение исключительной ситуации

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

Атомарные операции (Atomic operations)

4747

Атомарная операция (Atomic operation) – это операций,

которая в любой момент времени выполняется только

одним потоком

Атомарные операции намного “легче” мьютексов –

не требуют блокирования потоков

TBB поддерживаем атомарные переменные

atomic<T> AtomicVariableName

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

Атомарные операции (Atomic operations)

4848

Операции над переменной 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

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

Атомарные операции (Atomic operations)

4949

atomic<int> counter;

unsigned int GetUniqueInteger() {

return counter.fetch_and_add(1);}

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

Атомарные операции (Atomic operations)

5050

atomic<int> Val;

int UpdateValue()

{

do {

v = Val;

newv = f(v);

} while(Val.compare_and_swap(newv, v) != v);

return v;

}

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

Аллокаторы памяти

5151

Intel TBB предоставляет два аллокатора

памяти (альтернативы STL std::allocator)

scalable_allocator<T> – обеспечивает параллельное

выделение памяти нескольким потокам

cache_aligned_allocator<T> – обеспечивает выделение

блоков памяти, выравненных на границу длины кеш-

линии (cacheline)

Это позволяет избежать ситуации когда потоки на разных

процессорах пытаются модифицировать разные слова

памяти, попадающие в одну строку кэша, и как следствие,

постоянно перезаписываемую из памяти в кеш

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

Аллокаторы памяти

5252

/* STL vector будет использовать аллокатор TBB */std::vector<int, cache_aligned_allocator<int> > v;

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

Ссылки

5353

James Reinders. Intel Threading Building Blocks.

– O'Reilly, 2007. – 336p.

Intel Threading Building Blocks Documentation //

http://software.intel.com/sites/products/documentation/docli

b/tbb_sa/help/index.htm


Recommended