Intel Threading Building Blocks 4.4 – что нового?
Недавно вышло большое обновление Intel Parallel Studio XE 2016, и вместе с ним Intel Threading Building Blocks 4.4. В новой версии появилось несколько интересных дополнений:
- Глобальный контроль для управления ресурсами, в первую очередь, количеством рабочих потоков.
- Новые типы узлов Flow Graph: composite_node и async_node. Кроме того, во Flow Graph была улучшена функциональность сброса (reset).
- Больше фишек из С++11 для лучшей производительности.
Бывает много случаев, когда нужно ограничить число рабочих потоков параллельного алгоритма. Intel TBB позволяет это сделать через инициализацию объекта tbb::task_scheduler_init, указав в параметре желаемое количество потоков:
tbb::task_scheduler_init my_scheduler(8);
Однако, приложение может иметь сложную структуру. Например, множество плагинов, или потоков, каждый из которых может использовать свою версию Intel TBB. В этом случае будет несколько объектов tbb::task_scheduler_init, и созданием нового дело не исправишь.
Для решения таких проблем появился класс tbb::global_control. Создание объекта такого класса с параметром global_control::max_allowed_parallelism ограничивает количество активных потоков Intel TBB глобально. В отличие от the tbb::task_scheduler_init, это ограничение сразу становится общим для всего процесса, даже если библиотека уже инициализирована в других модулях или потоках. Уже созданные потоки, конечно, не исчезнут, но активно работать одновременно будет столько, сколько указано, остальные будут ждать.
#include "tbb/parallel_for.h"
#include "tbb/task_scheduler_init.h"
#define TBB_PREVIEW_GLOBAL_CONTROL 1
#include "tbb/global_control.h"
using namespace tbb;
void foo()
{
// The following code could use up to 16 threads.
task_scheduler_init tsi(16);
parallel_for( . . . );
}
void bar()
{
// The following code could use up to 8 threads.
task_scheduler_init tsi(8);
parallel_for( . . . );
}
int main()
{
{
const size_t parallelism = task_scheduler_init::default_num_threads();
// total parallelism that TBB can utilize is cut in half for the dynamic extension
// of the given scope, including calls to foo() and bar()
global_control c(global_control::max_allowed_parallelism, parallelism/2);
foo();
bar();
} // restore previous parallelism limitation, if one existed
}
В этом примере функции foo() и bar() инициализируют TBB task scheduler локально. При этом объект global_control в main() устанавливает верхний лимит одновременно работающих потоков. Будь у нас ещё один task_scheduler_init вместо глобального контроля, ре-инициализация Intel TBB в foo() и bar() не произошла бы, т.к. главный поток уже имел бы активный task_scheduler_init. Локальные установки в foo() и bar() были бы проигнорированы, обе функции использовали бы ровно то число потоков, которое было установлено в main(). C global_control мы жёстко ограничиваем верхний предел (например, не больше 8 потоков), но это не мешает инициализировать библиотеку локально с меньшим числом потоков.
Объекты global_control могут быть вложенными. Когда мы создаём новый, он переписывает лимит потоков в меньшую сторону, в большую не может. Т.е. Если сначала создали global_control с 8 потоками, потом с 4, то ограничение будет 4. А если сначала с 8, потом с 12, ограничение будет 8. А когда объект global_control удаляется, восстанавливается предыдущее значение, т.е. минимум из установок всех «живых» объектов глобального контроля.
tbb::global_control пока является preview feature в Intel TBB 4.4. Кроме количества потоков, этот класс позволяет ограничить размер стэка для потоков через параметр thread_stack_size.
Новый тип узла tbb::flow::composite_node позволяет «упаковывать» любое количество других узлов. Большие приложения с сотнями узлов могут быть лучше структурированы, собираясь из нескольких крупных блоков tbb::flow::composite_node, с определёнными интерфейсами входа и выхода.
Пример на картинке выше использует composite_node для инкапсуляции двух узлов, join_node и function_node. Концепция в том, чтобы продемонстрировать, что сумма первых n положительных нечётных чисел равна n в квадрате.
Сначала создаём класс adder. В нём есть join_node j с двумя входами и function_node f. j получает число на каждый из своих входов, и отсылает tuple из этих чисел на вход f, который складывает числа. Для инкапсуляции этих двух узлов adder наследуется от типа composite_node с двумя входами и одним выходом, что соответствует двум входам j и одному выходу f:
#include "tbb/flow_graph.h"
#include <iostream>
#include <tuple>
using namespace tbb::flow;
class adder : public composite_node< tuple< int, int >, tuple< int > > {
join_node< tuple< int, int >, queueing > j;
function_node< tuple< int, int >, int > f;
typedef composite_node< tuple< int, int >, tuple< int > > base_type;
struct f_body {
int operator()( const tuple< int, int > &t ) {
int n = (get<1>(t)+1)/2;
int sum = get<0>(t) + get<1>(t);
std::cout << "Sum of the first " << n <<" positive odd numbers is " << n <<" squared: " << sum << std::endl;
return sum;
}
};
public:
adder( graph &g) : base_type(g), j(g), f(g, unlimited, f_body() ) {
make_edge( j, f );
base_type::input_ports_type input_tuple(input_port<0>(j), input_port<1>(j));
base_type::output_ports_type output_tuple(f);
base_type::set_external_ports(input_tuple, output_tuple);
}
};
Дальше создаём split_node s, который будет служить источником положительных нечётных чисел. Используем первые 4 таких числа: 1, 3, 5 и 7. Создаём три объекта adder: a0, a1 и a2. Adder a0 получает 1 и 3 от split_node. Они складываются, и сумма отправляется к a1. Второй adder a1 получает сумму 1 и 3 с одного входного порта, и 5 со второго от split_node. Эти значения тоже складываются, и сумма отправляется в a2. Таким же образом, третий adder a2 получает сумму 1, 3 и 5 с одного входа и 7 со второго входа от split_node. Каждый adder пишет сумму, которую он вычислил, которая равна квадрату количества чисел в момент выполнения adder в графе.
int main() {
graph g;
split_node< tuple<int, int, int, int> > s(g);
adder a0(g);
adder a1(g);
adder a2(g);
make_edge(output_port<0>(s), input_port<0>(a0));
make_edge(output_port<1>(s), input_port<1>(a0));
make_edge(output_port<0>(a0),input_port<0>(a1));
make_edge(output_port<2>(s), input_port<1>(a1));
make_edge(output_port<0>(a1), input_port<0>(a2));
make_edge(output_port<3>(s), input_port<1>(a2));
s.try_put(std::make_tuple(1,3,5,7));
g.wait_for_all();
return 0;
}
Шаблонный класс async_node позволяет асинхронно работать с активностями, происходящими за пределами пула потоков Intel TBB. Например, если ваше Flow Graph приложение должно общаться со сторонним потоком, рантаймом или устройством, async_node может стать полезным. Он имеет интерфейсы для отправки результата обратно, поддерживая двустороннюю асинхронную коммуникацию между TBB Flow Graph и внешней для него сущностью. async_node является preview feature в Intel TBB 4.4.
Теперь можно сбрасывать состояние Flow Graph после некорректной остановки, например, выброшенного исключения или явной остановки (cancel) с помощью вызова tbb::flow::graph::reset(reset_flags f). Для удаления всех рёбер графа используйте флаг reset(rf_clear_edges), для сброса всех функциональных объектов — флаг reset(rf_reset_bodies).
Ещё добавились (как preview) следующие операции над графом:
- Вырезание одного узла из графа
- Получение количества «предшественников» и «последователей» узла
- Получение копии всех «предшественников» и «последователей» узла
Операции перемещения (move operations) C++11 помогают избежать ненужного копирования данных. В Intel TBB 4.4 появились move-aware insert и emplace методы для контейнеров concurrent_unordered_map и concurrent_hash_map. concurrent_vector::shrink_to_fit был оптимизирован для типов, поддерживающих C++11 move semantics.
Контейнер tbb::enumerable_thread_specific получил move constructor и оператор присваивания. Локальные значения потока теперь могут быть сконструированы с произвольным числом аргументов с помощью конструктора, использующего variadic templates.
Заголовочный файл tbb/compat/thread автоматически включает C++11 где возможно. “Exact exception propagation” появилось для Intel C++ Compiler под OS X*.
Вы можете скачать последнюю версию Intel TBB c open source или коммерческого сайтов.