В предыдущей статье об Intel Threading Building Blocks рассказывалось о преимуществах этой библиотеки, а также был рассмотрен пример алгоритма parallel_for. Рассмотрим пример использования алгоритма parallel_reduce, который отличается от функции parallel_for тем, что позволяет объединить частичные результаты, полученные в парралельных потоках.
Прежде всего, нужно подключить необходимые заголовочные файлы:
#include <stdio.h>
#include <tbb/blocked_range.h>
#include <tbb/parallel_reduce.h>
#include <tbb/task_scheduler_init.h>
* This source code was highlighted with Source Code Highlighter.
Здесь stdio.h используется для включения функции printf (вывод на экран), blocked_range.h для использования класса blocked_range (блочный диапазон), parallel_reduce.h для включения соответствующего алгоритма, а task_scheduler_inti.h — для включения планировщика задач.
Как было сказано ранее, стандартные алгоритмы Intel TBB — parallel_for, parallel_reduce — используют концепцию класса-задачи. В примере ниже показан подобный класс для решения задачи нахождения значения константы Пи (пример навеян конкурсом Интела, а также самостоятельной работой моего студента). Суть нахождения числа заключается в вычислении площади криволинейной трапеции, которая примерно описана большим количеством прямоугольников. Чем больше прямоугольников, тем точнее значение, но тем и больше времени требуется для вычисления. Иллюстрацию к этой задаче можно посмотреть на сайте «Вольфрам-Альфа».
// Класс-задача, отвечает за подсчет значения пи на заданном диапазаоне
class PiCalculation {
private:
// Количество шагов в интервале, влияет на точность
long num_steps; // Ширина шага double step; public: // Частичное значение пи double pi; // Вычислить частичное значение пи на заданном диапазоне void operator () (const tbb::blocked_range<long> &r) { double sum = 0.0; long end = r.end(); for (int i = r.begin(); i != end; i++) { double x = (i + 0.5) * step; sum += 4.0/(1.0 + x * x); } pi += sum * step; } // Объеденить частичные результаты void join(PiCalculation &p) { pi += p.pi; } // Разделяющий конструктор PiCalculation(PiCalculation &p, tbb::split) { pi = 0.0; num_steps = p.num_steps; step = p.step; } // Конструктор PiCalculation(long steps) { pi = 0.0; num_steps = steps; step = 1./(double)num_steps; } }; * This source code was highlighted with Source Code Highlighter.
Важными в этом классе есть следующие два метода: operator () и join.
Метод void operator () (const blocked_range &r) вычисляет частичный результат в интервале [r.begin, r.end). Библиотека Intel TBB и класс blocked_range заботятся о том, чтобы рекурсивно разбить исходное пространство итераций и «скармливать» небольшими порциями методу operator (). Результат накапливается в переменной pi, которая частная для потока. Дается гарантия, что operator () для одного класса будет вызван только из одного потока, поэтому ошибки типа «гонки за данными» (data races, race conditions) исключены.
Метод void join (PiCalculation &p) суммирует частные результаты различных потоков. Здесь проявляется важное отличие библиотеки Intel TBB от библиотек OpenMP и MPI, в которых количество операций для объединения частичных результатов (reduction, редуцирование) ограничено. Intel TBB позволяет реализовать произвольные функции объединения результатов в теле метода join.
Рассмотрим запуск алгоритма parallel_reduce.
int main() { // Инициализация библиотеки Intel TBB tbb::task_scheduler_init init; // Количество итераций const long steps = 100000000; // Создание объекта-задачи по вычислению пи // Передается параметр: количество итераций PiCalculation pi(steps); // Запуск алгоритма parallel_reduce над диапазоном [0, steps) tbb::parallel_reduce(tbb::blocked_range<long>(0, steps, 1000000), pi); printf ("Pi is %3.20f\n", pi.pi); return 0; } * This source code was highlighted with Source Code Highlighter.
Для этого необходимо выполнить следующие шаги: создать объект планировщика задач Intel TBB, создать объект задачи PiCalculation, и вызвать функцию parallel_reduce. Здесь функция принимает два параметра. Первый — это класс диапазона. В нашем случае мы указывает количество итераций [0, steps), т. е. при вычислении интеграла от 0 до 1 будет использовано steps = 100 000 000 шагов. Второй параметр — это ссылка на объект задачи, в котором после возвращения из функции parallel_reduce будет содержаться значение пи.
Комментариев нет:
Отправить комментарий