Top Banner
Курносов Михаил Георгиевич E-mail: [email protected] WWW: www.mkurnosov.net Курс “Высокопроизводительные вычислительные системы” Сибирский государственный университет телекоммуникаций и информатики (Новосибирск) Осенний семестр, 2014 Лекция 8 Intel Threading Building Blocks
53

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

Jul 02, 2015

Download

Software

Intel Threading Building Blocks
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
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