OpenMP/C++:并行 for 循环,之后减少 - 最佳实践?

Posted

技术标签:

【中文标题】OpenMP/C++:并行 for 循环,之后减少 - 最佳实践?【英文标题】:OpenMP/C++: Parallel for loop with reduction afterwards - best practice? 【发布时间】:2015-06-19 16:58:15 【问题描述】:

给定以下代码...

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&lt;T&gt; velocity 在内部循环中被修改。多个线程可以同时访问和(尝试)修改velocity 的同一元素。

我认为第一个解决方案是在velocity[j] += f(j);操作之前写#pragma omp atomic。这给了我一个编译错误(可能与类型为 Eigen::Vector3dvelocity 的元素是类成员有关)。另外,我读到原子操作非常,与每个线程都有一个私有变量并最终减少。这就是我想做的,我想。

我想出了这个:

#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 是班级成员。

我试图找到一个类似问题的问题,和this问题看起来几乎一样。但我认为我的情况可能有所不同,因为最后一步包含for 循环。这是否是最好的方法的问题仍然存在。

编辑:根据评论请求:reduction 子句...

    #pragma omp parallel reduction(+:velocity)
    for (omp_int i = 0; i < velocity_local.size(); ++i)
        velocity[i] += velocity_local[i];

...抛出以下错误:

错误 C3028: 'ShapeMatching::velocity' : 在数据共享子句中只能使用变量或静态数据成员

(与g++类似的错误)

【问题讨论】:

使用减少错误共享代码,以便建议修复。 @Jeff 完成。 [足够的字符] 你考虑过ppl吗?编写“自我减少数据”的代码在那里很流畅,不必是原语。基本上你描述了线程加载数据是什么,以及如何组合两个线程本地数据,其余的由它来完成。 OpenMP 不知道如何减少 STL 容器。我不记得是否支持简单数组。 当您自己进行缩减时,您需要将 velocity[j] += f(j); 更改为 `velocity_local[j] += f(j); 【参考方案1】:

你正在做一个数组缩减。我已经多次描述了这一点(例如reducing an array in openmp 和fill histograms array reduction in parallel with openmp without using a critical section)。您可以使用或不使用关键部分来执行此操作。

您已经使用关键部分正确完成了此操作(在您最近的编辑中),所以让我描述如何在没有关键部分的情况下执行此操作。


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];
        
    

由于我没有做过的错误共享,此方法需要额外的注意/调整。

至于哪种方法更好,你需要测试。

【讨论】:

以上是关于OpenMP/C++:并行 for 循环,之后减少 - 最佳实践?的主要内容,如果未能解决你的问题,请参考以下文章

我要求我的并行 OpenMP C 代码的执行时间解决方案

向量的并行求和

控制并行循环中的线程数并减少开销

openMP 嵌套并行 for 循环与内部并行 for

R中的并行化“查找”循环

并行for循环R