OpenMP/C++:并行 for 循环,之后缩减 - 最佳实践
OpenMP/C++: Parallel for loop with reduction afterwards - best practice?
给定以下代码...
for (size_t i = 0; i < clusters.size(); ++i)
{
const std::set<int>& cluster = clusters[i];
// ... expensive calculations ...
for (int j : cluster)
velocity[j] += f(j);
}
。我想在多个 CPU/内核上运行。函数f
不使用velocity
。
第一个 for 循环之前的简单#pragma omp parallel for
将产生不可预测/错误的结果,因为std::vector<T> velocity
在内部循环中被修改。多个线程可以同时访问和(尝试)修改velocity
的同一元素。
我认为第一个解决方案是在velocity[j] += f(j);
操作之前编写#pragma omp atomic
。这给了我一个编译错误(可能与元素的类型Eigen::Vector3d
或velocity
类成员有关)。此外,我读到与为每个线程都有一个私有变量并最终进行缩减相比,原子操作非常慢。所以这就是我想做的,我想。
我想出了这个:
#pragma omp parallel
{
// these variables are local to each thread
std::vector<Eigen::Vector3d> velocity_local(velocity.size());
std::fill(velocity_local.begin(), velocity_local.end(), Eigen::Vector3d(0,0,0));
#pragma omp for
for (size_t i = 0; i < clusters.size(); ++i)
{
const std::set<int>& cluster = clusters[i];
// ... expensive calculations ...
for (int j : cluster)
velocity_local[j] += f(j); // save results from the previous calculations
}
// now each thread can save its results to the global variable
#pragma omp critical
{
for (size_t i = 0; i < velocity_local.size(); ++i)
velocity[i] += velocity_local[i];
}
}
这是一个很好的解决方案吗?这是最好的解决方案吗?(甚至正确吗?
进一步的想法:使用 reduce
子句(而不是critical
部分)会引发编译器错误。我认为这是因为velocity
是班级成员。
我试图找到一个有类似问题的问题,这个问题看起来几乎是一样的。但我认为我的情况可能会有所不同,因为最后一步包括一个for
循环。此外,这是否是最佳方法的问题仍然存在。
编辑:根据每个评论的要求:reduction
条款...
#pragma omp parallel reduction(+:velocity)
for (omp_int i = 0; i < velocity_local.size(); ++i)
velocity[i] += velocity_local[i];
。引发以下错误:
错误 C3028:"形状匹配::速度":数据共享子句中只能使用变量或静态数据成员
(与g++
类似的错误)
您正在执行数组缩减。我已经多次描述了这一点(例如,在 openmp 中减少数组并与 openmp 并行填充直方图数组缩减,而无需使用关键部分)。 您可以在有和没有关键部分的情况下执行此操作。
您已经使用关键部分(在您最近的编辑中)正确完成了这一点,所以让我描述如何在没有关键部分的情况下执行此操作。
std::vector<Eigen::Vector3d> velocitya;
#pragma omp parallel
{
const int nthreads = omp_get_num_threads();
const int ithread = omp_get_thread_num();
const int vsize = velocity.size();
#pragma omp single
velocitya.resize(vsize*nthreads);
std::fill(velocitya.begin()+vsize*ithread, velocitya.begin()+vsize*(ithread+1),
Eigen::Vector3d(0,0,0));
#pragma omp for schedule(static)
for (size_t i = 0; i < clusters.size(); i++) {
const std::set<int>& cluster = clusters[i];
// ... expensive calculations ...
for (int j : cluster) velocitya[ithread*vsize+j] += f(j);
}
#pragma omp for schedule(static)
for(int i=0; i<vsize; i++) {
for(int t=0; t<nthreads; t++) {
velocity[i] += velocitya[vsize*t + i];
}
}
}
由于我没有做过的错误共享,此方法需要额外的小心/调整。
至于哪种方法更好,您必须进行测试。
- 在C#中处理C++指针而不使用unsafe的最佳方法
- 为什么在popback()操作之后,它仍然打印完整的矢量
- 在C++中,将大的无符号浮点数四舍五入为整数的最佳方法是什么
- 实现无开销push_back的最佳方法是什么
- 在类定义之后定义一个私有方法
- 在c代码之间共享数据的最佳方式
- 使用std::source_location报告错误的最佳实践
- 在循环C++中指定字符串之后,不会打印该字符串
- 派生类销毁的最佳实践是什么
- C++宏忽略之后的内容
- 要与"if constexpr"一起使用的编译时消息(在预处理器之后)
- 将寄存器设计成可由C和C++访问的外设的最佳实践
- 在两台机器之间进行时间戳的最佳c++chrono函数是什么
- 使用QQuickFramebufferObject时同步数据的最佳方式是什么
- 在C++中向零方向近似的最佳方法
- 使用不同的CRT将新的C++代码与旧的(二进制)组件隔离开来的最佳方法是什么
- 从嵌套在std::映射中的std::列表中删除元素的最佳方式
- 如果条件为TRUE(最佳方式?),则在do while循环中后置增量
- 检测win32服务创建和删除的最佳方法
- OpenMP/C++:并行 for 循环,之后缩减 - 最佳实践