У меня есть программа на C ++, которая может быть распараллелена. Я использую Visual Studio 2010, 32-битная компиляция.
Короче говоря, структура программы следующая
#define num_iterations 64 //some number
struct result
{
//some stuff
}
result best_result=initial_bad_result;
for(i=0; i<many_times; i++)
{
result *results[num_iterations];for(j=0; j<num_iterations; j++)
{
some_computations(results+j);
}
// update best_result;
}
Так как каждый some_computations()
является независимым (некоторые глобальные переменные прочитаны, но глобальные переменные не изменены) Я распараллелил внутренний for
-loop.
Моя первая попытка была с повышение :: нить,
thread_group group;
for(j=0; j<num_iterations; j++)
{
group.create_thread(boost::bind(&some_computation, this, result+j));
}
group.join_all();
Результаты были хорошими, но я решил попробовать больше.
Я попробовал OpenMP библиотека
#pragma omp parallel for
for(j=0; j<num_iterations; j++)
{
some_computations(results+j);
}
Результаты были хуже, чем boost::thread
х.
Тогда я попробовал госзакупках библиотека и используется parallel_for()
:
Concurrency::parallel_for(0,num_iterations, [=](int j) {
some_computations(results+j);
})
Результаты были худшими.
Я нашел это поведение довольно удивительным. Поскольку OpenMP и PPL предназначены для распараллеливания, я бы ожидал лучших результатов, чем boost::thread
, Я ошибся?
Почему boost::thread
дает мне лучшие результаты?
OpenMP или PPL не пессимистичны. Они просто делают, как им говорят, однако есть некоторые вещи, которые вы должны принять во внимание, когда пытаетесь паралеллизировать циклы.
Не видя, как вы реализовали эти вещи, трудно сказать, какова может быть настоящая причина.
Кроме того, если операции в каждой итерации имеют некоторую зависимость от любых других итераций в том же цикле, то это приведет к конфликту, который замедлит процесс. Вы не показали, что ваши some_operation
Функция на самом деле делает, поэтому трудно сказать, есть ли зависимости данных.
Цикл, который может быть по-настоящему распараллеленным, должен позволять запускать каждую итерацию полностью независимо от всех других итераций, при этом доступ к общей памяти не требуется ни на одной из итераций. Поэтому желательно, чтобы вы записали материал в локальные переменные, а затем скопировали в конце.
Не все Циклы могут быть распараллелены, это очень зависит от типа выполняемой работы.
Например, что-то, что хорошо для распараллеливания, это работа над каждым пикселем экранного буфера. Каждый пиксель полностью независим от всех других пикселей, и, следовательно, поток может выполнить одну итерацию цикла и выполнять работу без необходимости задерживать ожидание совместной памяти или зависимостей данных в цикле между итерациями.
Кроме того, если у вас есть непрерывный массив, этот массив может быть частично в строке кэша, и если вы редактируете элемент 5 в потоке A, а затем меняете элемент 6 в потоке B, вы можете получить конфликт в кэше, что также замедлит работу , поскольку они будут находиться в той же строке кэша. Феномен, известный как ложный обмен.
Есть много аспектов, о которых стоит подумать при выполнении распараллеливания циклов.
Короче говоря, openMP
в основном основан на разделяемой памяти, с дополнительными затратами на управление задачами и памятью. ppl
предназначен для обработки общих шаблонов общих структур данных и алгоритмов, что влечет за собой дополнительные затраты сложности. У них обоих есть дополнительная стоимость процессора, но ваше простое падение boost
темы нет (boost
потоки — это просто API-оболочка). Вот почему они оба медленнее, чем ваши boost
версия. И так как примерные вычисления независимы друг от друга, без синхронизации, openMP
должно быть близко к boost
версия.
Это происходит в простых сценариях, но для сложных сценариев, со сложным размещением данных и алгоритмами, это должно зависеть от контекста.