Если вы решите использовать Openmp 3.0
, вы можете использовать функцию task
:
#pragma omp parallel
#pragma omp single
{
for(auto it = l.begin(); it != l.end(); ++it)
#pragma omp task firstprivate(it)
it->process();
#pragma omp taskwait
}
Это выполнит цикл в одном потоке, но делегирует обработку элементов другим.
Без OpenMP 3.0
самый простой способ - записать все указатели на элементы в списке (или итераторы в векторе и выполнить итерации по этому. Таким образом, вам не придется ничего копировать обратно и избегать накладных расходов на копирование).сами элементы, так что не должно быть слишком много накладных расходов:
std::vector<my_element*> elements; //my_element is whatever is in list
for(auto it = list.begin(); it != list.end(); ++it)
elements.push_back(&(*it));
#pragma omp parallel shared(chunks)
{
#pragma omp for
for(size_t i = 0; i < elements.size(); ++i) // or use iterators in newer OpenMP
elements[i]->process();
}
Если вы хотите избежать копирования даже указателей, вы всегда можете создать распараллеленный цикл for вручную. Вы можете использовать потоки.получить доступ к чередующимся элементам списка (как предложено KennyTM) или разделить диапазон на примерно равные конические части перед итерацией и повторением по ним. Последнее кажется предпочтительным, поскольку потоки избегают доступа к узлам списка, обрабатываемым в настоящее время другими потоками (даже если только следующий указатель)), что может привести к ложному обмену. Это будет выглядеть примерно так:
#pragma omp parallel
{
int thread_count = omp_get_num_threads();
int thread_num = omp_get_thread_num();
size_t chunk_size= list.size() / thread_count;
auto begin = list.begin();
std::advance(begin, thread_num * chunk_size);
auto end = begin;
if(thread_num = thread_count - 1) // last thread iterates the remaining sequence
end = list.end();
else
std::advance(end, chunk_size);
#pragma omp barrier
for(auto it = begin; it != end; ++it)
it->process();
}
Барьер не является строго необходимым, однако, если process
изменяет обработанный элемент (то есть это не метод const), может произойти некое ложное совместное использование без него, если потоки повторяютсянад последовательностью, которая уже мутирует.Этот способ будет повторяться в 3 * n раз по последовательности (где n - количество потоков), поэтому масштабирование может быть меньше оптимального для большого числа потоков.
Чтобы уменьшить накладные расходы, вы можете поместить генерациюиз диапазонов за пределами #pragma omp parallel
, однако вам необходимо знать, сколько потоков образует параллельный раздел.Поэтому вам, вероятно, придется вручную установить num_threads
или использовать omp_get_max_threads()
и обработать случай, когда число созданных потоков будет меньше omp_get_max_threads()
(что является только верхней границей).Последний способ может быть обработан, возможно, назначив каждому потоку несколько кусков в этом случае (с помощью #pragma omp for
следует сделать это):
int max_threads = omp_get_max_threads();
std::vector<std::pair<std::list<...>::iterator, std::list<...>::iterator> > chunks;
chunks.reserve(max_threads);
size_t chunk_size= list.size() / max_threads;
auto cur_iter = list.begin();
for(int i = 0; i < max_threads - 1; ++i)
{
auto last_iter = cur_iter;
std::advance(cur_iter, chunk_size);
chunks.push_back(std::make_pair(last_iter, cur_iter);
}
chunks.push_back(cur_iter, list.end();
#pragma omp parallel shared(chunks)
{
#pragma omp for
for(int i = 0; i < max_threads; ++i)
for(auto it = chunks[i].first; it != chunks[i].second; ++it)
it->process();
}
Это займет всего три итерации по list
(две, если выможно получить размер списка без итераций).Я думаю, что это лучшее, что вы можете сделать для итераторов без произвольного доступа, не используя tasks
или итерируя по какой-то неестественной структуре данных (например, по указателю).