ArBB, Cilk+, OpenMP, OpenCL, TBB - День Знаний или День Выборов

Данный пост изначально был размещен в корпоративном блоге Intel на площадке Habrahabr.ru.

Вспомните, делали ли вы в последние годы какой-нибудь существенный выбор, не почитав предварительно в интернете отзывов тех, кто уже выбрал это ранее?

Мы живем в эпоху «развивающегося отзывизма».
Почти все покупки, выбор места отдыха, работы, ученья и леченья, банка и танка, фильма и фирмы… Лучше прочесть один отзыв, чем тысячи реклам.

Хотя, скорее всего, отзывами вы пользовались не во всех случаях.
Например, при выборе спутника\спутницы жизни.
Или при выборе кабинки в общественном туалете :).
Или – при выборе способа распараллеливания программ. В последнем случае - наверное, просто потому, что таких отзывов вам не попадалось. Попробую это исправить. А именно, расскажу, чего ждать от Cilk+, OpenMP, OpenCL, TBB и ArBB(Ct) с точки зрения их возможностей, простоты освоения и использования, качества документации, а также ожидаемой производительности.

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

OpenMP



Набор прагм компилятора для автоматического распараллеливания (также можно использовать дополнительные библиотечные функции).
Поддерживается всеми основными компиляторами, иначе - просто игнорируется, не вызывая ошибок.
Работает на всех распространенных архитектурах (в теории – и на ARM) и ОС (в теории – даже на Android).

Документация хорошая и есть везде - вплоть до глянцевых журналов.

Простота изучения - исключительная. Время освоения и имплементации – минуты. Для сложного проекта – не более часа.

Отладка – ограничена, но возможна – см. Intel Parallel Studio.

Недостатки: не использует GPU, не поддерживает многие нужные для «продвинутого» контроля и синхронизации потоков функции; не всегда обеспечивает хорошую производительность; неоптимально «комбинируется» в различных компонентах приложения и с другими средствами распараллеливания.

Пример. Цикл for с использованием OpenMP будет выглядеть так:

#pragma omp parallel for

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

     result[i] = a[i] * b[i];

OpenCL(Open Computing Language)



Молодой (2008г рождения) стандарт гетерогенного параллельного программирования. То есть, внутри использует не только CPU, но и GPU, что является его главным, а может, и единственным преимуществом. Поддерживается AMD, Apple, ARM, Intel, NVidia и всеми компаниями-производителями мобильных телефонов. Не поддерживается Microsoft.
Еще одно декларируемое достоинство OpenCL – автоматическая векторизация кода при наличии возможностей железа. Но того же эффекта можно добиться и не используя OpenCL :)

Концепция стандарта: центральный хост и множество OpenCL устройств (Compute Unit), параллельно выполняющих определенные программистом функции-ядра (kernel). Кернелы пишутся на диалекте языка С (С с некоторыми ограничениями и дополнениями) и компилируются специальным компилятором.
Основная программа, выполняемая на хосте, должна много всего и всем :):

    • создать окружение OpenCL,

    • создать кернелы,

    • определить платформу выполнения = контексты, устройства и очереди,

    • создать и построить программу (динамическую библиотеку для кернелов),

    • выделить и инициализировать объекты в памяти,

    • определить ядра и присоединить их к их аргументам, и, наконец, передать память и ядра на выполнение OpenCL устройствам.



Документация: очень мало.
Самое сложное в изучении\имплементировании средство, особенно учитывая недостаток документации и, особенно, примеров. Время освоения и имплементации – дни. Для сложного проекта – недели.

Прочие недостатки: не лучшая производительность на имеющихся примерах, использующих только CPU, трудности отладки и поиска проблем с производительностью.

Пример. Цикл for из функции

void scalar_mul(int n, const float *a, const float *b, float *result) { 

   int i; 

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

       result[i] = a[i] * b[i];

 } 



в переводе на OpenCL превратиться в кернел:

__kernel void scalar_mul(__global const float *a, __global const float *b, 

__global float *result) { 

         size_t id = get_global_id(0); 

          result[id] = a[id] * b[id]; 

}



… плюс понадобится еще полный экран кода (!) для основной программы-хоста (см. выше). Этот код, вызывающий специальные OpenCL функции, конечно, можно списать из примера Intel OpenCL SDK, но разбираться в нем все равно придется.

Три следующие средства распараллеливания программ образуют Inel Parallel Building Blocks:
Cilk Plus, TBB, ArBB.

Cilk Plus


«Плюс» означает расширения Cilk для работы с массивами.
Расширение С\С++, поддерживаемое исключительно компиляторами Intel (Windows, Linux) и, с августа 2011, - gcc 4.7, что сразу показывает недостаток – отсутствие реальной кросс-платформености. GPU также не поддерживается.

Достоинства:
Простота освоения. Cilk немногим сложнее OpenMP. Вводится в проект за считанные минуты, в худшем случае – часы работы.
При этом, производительность превосходит OpenMP. Кроме того, Сilk хорошо комбинируется со всеми Inel Parallel Building Blocks и OpenCL.
При использовании Intel Cilk SDK изменения кода минимальны, всю работу выполняет компилятор «за сценой».

Документация: немного, но, учитывая простоту Cilk, это не проблема.

Пример. Ваш любимый цикл for на Cilk будет выглядеть так:

cilk_for (i = 0; i < n; ++i) {

      result[i] = a[i] * b[i];

 } 

TBB (Threading Building Blocks)



Созданная Intel библиотека С++ темплейтов для параллельного программирования. Работает на Linux, Windows, Mac и даже на Xbox360, не требует компилятора Intel. Недостаток: не использует GPU.
В библиотеке реализованы:

    • потокобезопасные контейнеры: вектор, очередь, хеш-таблица;

    • параллельные алгоритмы for, reduce, scan, pipeline, sort и тд.

    • масштабируемые распределители памяти;

    • мьютексы и атомарные операции;



Документация и примеры: много, хорошего качества.

Простота освоения и использования. Для не имеющих опыта пользования шаблонами в коде – достаточно сложно. Для имеющих - тоже не всегда элементарно. Могут потребоваться значительные изменения кода. Время изучения\имплементации - от одного рабочего дня до недели.

Достоинства: отличная производительность, независимость от компилятора, возможность использовать отдельные компоненты библиотеки независимо.

Пример.

void scalar_mul(int n, const float *a, const float *b, float *result) { 

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

            result[i] = a[i] * b[i];

 }



с использованием ТВВ придется превратить в оператор() класса так называемого Body (тела цикла):

using namespace tbb;

class ApplyMul {

     public:

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

           float *a = my_a;

           float *b = my_b;

           float *result = my_result;


          for( size_t i=r.begin(); i!=r.end(); ++i )

            result[i] = a[i] * b[i];

      }

      ApplyMul( float a[],  float b[], float result []) :

      my_a(a), my_b(b), my_result(result)

      {}

};



Где blocked_range -темплейт-итератор, предоставляемый tbb.

И только после этого можно использовать tbb parallel_for

void parallel_mul ( float a[], float b[],float result[], int n ) {

     parallel_for(blocked_range<size_t>(0,n), ApplyMul(a, b, result));

}

ArBB (Array Building Blocks)



В девичестве – Ct, про который уже писал в этом блоге izard. Intel библиотека С++ шаблонов для параллельного программирования.
Intel ArBB работает на Windows* и Linux*, поддерживается компиляторами Intel, Microsoft Visual C++ и GCC. При наличии соответствующего run-nime должен работать на GPU и грядущей Intel MIC.

Достоинства: заложенная в дизайн полная поточная безопасность (отсутвие data race) использует внутри себя TBB, соответственно, хорошо комбинируется в разных модулях.

Документация: увы, немногословна, примеры – вполне нормальны.

Трудность освоения\имплементации – на уровне TBB.

Недостатки:
Все еще находится в стадии beta.
Спроектирован для обработки больших массивов данных, на небольших массивах за счет накладных расходов не обеспечивает хорошей производительности.
Пример:
Во что превратиться пресловутый цикл for в ArBB? При первом взгляде в документацию можно подумать, что в _for. Но нет. _for в ArBB – это как раз указание на то, что цикл имеет зависимости в итерациях и может выполняться только последовательно. «parallel for» в ArBB вообще нет.
А все тот же

 void scalar_mul(int n, const float *a, const float *b, float *result) { 

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

            result[i] = a[i] * b[i];

 } 



превратится в

void parallel_mul(dense<f32> a, dense<f32> b, dense<f32> &result) { 

    result = a * b; 

}



с вызовом

dense<f32> va; bind (va, a, n);

dense<f32> vb; bind (vb, b, n);

dense<f32> vres; bind (vres, result, n);


call(parallel_mul)(va, vb, vres);

Резюме №1


Сводная таблица выбора.
Сравнение ArBB OpenCL Cilk OpenMP TBB

Резюме №2



Горизонтальные прямые на картинке в начале этого поста – параллельны. Хотя, возможно, что по отзывам наблюдателей окажется, что нет.

Tags:
Nähere Informationen zur Compiler-Optimierung finden Sie in unserem Optimierungshinweis.