如何使用 OpenMP 通过 C++ std::list 并行化 for 循环
How do I parallelize a for loop through a C++ std::list using OpenMP?
我想使用 OpenMP 以并行方式遍历 std::list 中的所有元素。循环应该能够更改列表的元素。有没有一个简单的解决方案?当迭代器是随机访问迭代器时,OpenMP 3.0 似乎支持并行 for 循环,但不是其他。无论如何,我更喜欢使用 OpenMP 2.0,因为我无法完全控制哪些编译器可供我使用。
如果我的容器是一个向量,我可能会使用:
#pragma omp parallel for
for (auto it = v.begin(); it != v.end(); ++it) {
it->process();
}
我知道我可以将列表复制到向量中,执行循环,然后将所有内容复制回来。但是,如果可能的话,我想避免这种复杂性和开销。
如果您决定使用 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
或迭代一些不合适的数据结构(如指针向量(。
我怀疑这是否可能,因为你不能在不遍历列表的情况下跳到列表的中间。列表不存储在连续内存中,并且 std::list 迭代器不是随机访问。它们只是双向的。
http://openmp.org/forum/viewtopic.php?f=3&t=51
#pragma omp parallel
{
for(it= list1.begin(); it!= list1.end(); it++)
{
#pragma omp single nowait
{
it->compute();
}
} // end for
} // end ompparallel
这可以理解为展开为:
{
it = listl.begin
#pragma omp single nowait
{
it->compute();
}
it++;
#pragma omp single nowait
{
it->compute();
}
it++;
...
}
给定这样的代码:
int main()
{
std::vector<int> l(4,0);
#pragma omp parallel for
for(int i=0; i<l.size(); ++i){
printf("th %d = %d n",omp_get_thread_num(),l[i]=i);
}
printf("n");
#pragma omp parallel
{
for (auto i = l.begin(); i != l.end(); ++i) {
#pragma omp single nowait
{
printf("th %d = %d n",omp_get_thread_num(),*i);
}
}
}
return 0;
}
导出OMP_NUM_THREADS=4,输出如下(注意第二部分,工作线程编号可以重复(:
th 2 = 2
th 1 = 1
th 0 = 0
th 3 = 3
th 2 = 0
th 1 = 1
th 2 = 2
th 3 = 3
在不使用 OpenMP 3.0 的情况下,您可以选择让所有线程遍历列表:
std::list<T>::iterator it;
#pragma omp parallel private(it)
{
for(it = list1.begin(); it!= list1.end(); it++)
{
#pragma omp single nowait
{
it->compute();
}
}
}
在这种情况下,每个线程都有自己的迭代器副本(私有(,但只有一个线程将访问特定元素(单个(,而其他线程将向前移动到下一个项目(nowait(
或者,您可以循环一次以构建一个指针向量,然后在线程之间分发:
std::vector< T*> items;
items.reserve(list.size());
//put the pointers in the vector
std::transform(list.begin(), list.end(), std::back_inserter(items),
[](T& n){ return &n; }
);
#pragma omp parallel for
for (int i = 0; i < items.size(); i++)
{
items[i]->compute();
}
根据您的具体情况,一个或另一个可以更快。测试哪一个更适合您很容易。
这是一个允许并行插入/删除列表新元素的解决方案。
对于包含N
元素的列表,我们首先将列表剪切成nthreads
列表大致有N/nthreads
元素。在并行区域中,可以像这样完成此操作
int ithread = omp_get_thread_num();
int nthreads = omp_get_num_threads();
int t0 = (ithread+0)*N/nthreads;
int t1 = (ithread+1)*N/nthreads;
std::list<int> l2;
#pragma omp for ordered schedule(static)
for(int i=0; i<nthreads; i++) {
#pragma omp ordered
{
auto it0 = l.begin(), it1 = it0;
std::advance(it1, t1-t0);
l2.splice(l2.begin(), l2, it0, it1);
}
}
其中l2
是每个线程的切割清单。
然后我们可以并行处理每个列表。例如,我们可以像这样在列表中的每个第一个位置插入 -1
auto it = l2.begin();
for(int i=(t0+4)/5; i<(t1+4)/5; i++) {
std::advance(it, 5*i-t0);
l2.insert(it, -1);
}
最后,在我们并行操作列表之后,我们将每个线程的列表拼接回一个列表,如下所示:
#pragma omp for ordered schedule(static)
for(int i=0; i<nthreads; i++) {
#pragma omp ordered
l.splice(l.end(), l, l2.begin(), l2.end());
}
算法本质上是。
- 快进通过列表顺序制作切割列表。
- 并行添加、修改或删除元素处理切割清单。 将
- 修改后的切割列表按顺序拼接在一起。
这是一个工作示例
#include <algorithm>
#include <iostream>
#include <list>
#include <omp.h>
int main(void) {
std::list<int> l;
for(int i=0; i<22; i++) {
l.push_back(i);
}
for (auto it = l.begin(); it != l.end(); ++it) {
std::cout << *it << " ";
} std::cout << std::endl;
int N = l.size();
#pragma omp parallel
{
int ithread = omp_get_thread_num();
int nthreads = omp_get_num_threads();
int t0 = (ithread+0)*N/nthreads;
int t1 = (ithread+1)*N/nthreads;
//cut list into nthreads lists with size=N/nthreads
std::list<int> l2;
#pragma omp for ordered schedule(static)
for(int i=0; i<nthreads; i++) {
#pragma omp ordered
{
auto it0 = l.begin(), it1 = it0;
std::advance(it1, t1-t0);
l2.splice(l2.begin(), l2, it0, it1);
}
}
//insert -1 every 5th postion
auto it = l2.begin();
for(int i=(t0+4)/5; i<(t1+4)/5; i++) {
std::advance(it, 5*i-t0);
l2.insert(it, -1);
}
//splice lists in order back together.
#pragma omp for ordered schedule(static)
for(int i=0; i<nthreads; i++) {
#pragma omp ordered
l.splice(l.end(), l, l2.begin(), l2.end());
}
}
for (auto it = l.begin(); it != l.end(); ++it) {
std::cout << *it << " ";
} std::cout << std::endl;
}
结果
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
-1 0 1 2 3 4 -1 5 6 7 8 9 -1 10 11 12 13 14 -1 15 16 17 18 19 -1 20 21
- 如何使用OpenMP并行化此矩阵时间矢量运算
- 如何使用 MPI 的远程内存访问 (RMA) 功能并行化数据聚合?
- 在C++中使用并行化的预期速度是多少(不是 OpenMp,而是 <thread>)
- 如何使用 OpenMP 并行化最近邻搜索
- Malloc 在使用线程并行化 SSH 调用时存在问题
- 如何使用 OpenMP 正确并行化 for 循环?
- 如何将矩阵的行随机复制到内存中的另一个矩阵的过程并行化?
- 如何使用 Pthreads 并行化图像翻转?
- MPI:反复并行化缓冲区
- 是否可以使用OpenMP并行化一个列表,该列表可以在每次迭代中添加新元素
- 如何在Visual Studio中并行化armadillo
- 嵌套循环 OpenMP 并行化、私有索引还是公共索引?
- 如何并行化增加循环的大小
- 在 C++ 中使用 OpenMP 并行化两个 for 循环不会提供更好的性能
- OpenMP C++:并行化 for 循环的负载不平衡
- OpenMP 条件并行化 - 并行部分中 if 子句的语法
- C++ 犰狳和OpenMp:外积求和的并行化 - 定义犰狳矩阵的约简
- 将 for 循环与嵌套的 while 循环并行化时出现 OpenMP 分段错误
- 迭代卡拉苏巴算法在C++中使用OpenACC并行化和矢量化
- 如何使用 OpenMP 通过 C++ std::list 并行化 for 循环