C++ OpenMP 并行 For 循环 - std::vector 的替代品 [关闭]

Posted

技术标签:

【中文标题】C++ OpenMP 并行 For 循环 - std::vector 的替代品 [关闭]【英文标题】:C++ OpenMP Parallel For Loop - Alternatives to std::vector [closed] 【发布时间】:2013-09-11 05:36:57 【问题描述】:

基于此线程 OpenMP and STL vector,哪些数据结构是并行 for 循环中 共享 std::vector 的良好替代品?主要方面是速度,并且向量可能需要在循环期间调整大小。

【问题讨论】:

给我们看一些代码,描述你的具体情况......向量中将存储什么?你的循环会用它做什么?无论如何,使用std::vector 很可能是完全安全的。 正如链接线程中所说,您只需要关心在循环中调整矢量大小并可能重新分配时不使用 std::vector 。如果您只是更改对象,则可以很好地使用它。您能否详细说明您的要求,以及为什么 vector 不能满足您的需求? 我认为只有std::vector 被共享才会有问题。如果它是私有的,那么我认为使用push_backresize 没有问题。 【参考方案1】:

我认为您可以在大部分时间将std::vector 与 OpenMP 一起使用,并且仍然具有良好的性能。例如下面的代码并行填充std::vectors,然后将它们组合到最后。只要您的主循环/填充功能是瓶颈,这通常应该可以正常工作并且是线程安全的。

std::vector<int> vec;
#pragma omp parallel

    std::vector<int> vec_private;
    #pragma omp for nowait //fill vec_private in parallel
    for(int i=0; i<100; i++) 
        vec_private.push_back(i);
    
    #pragma omp critical
    vec.insert(vec.end(), vec_private.begin(), vec_private.end());

编辑:

OpenMP 4.0 允许使用#pragma omp declare reduction 进行用户定义的缩减。上面的代码可以用to来简化

#pragma omp declare reduction (merge : std::vector<int> : omp_out.insert(omp_out.end(), omp_in.begin(), omp_in.end()))

std::vector<int> vec;
#pragma omp parallel for reduction(merge: vec)
for(int i=0; i<100; i++) vec.push_back(i);

编辑: 到目前为止我所展示的内容并没有按顺序填充向量。如果顺序很重要,那么可以这样完成

std::vector<int> vec;
#pragma omp parallel

    std::vector<int> vec_private;
    #pragma omp for nowait schedule(static)
    for(int i=0; i<N; i++)  
        vec_private.push_back(i);
    
    #pragma omp for schedule(static) ordered
    for(int i=0; i<omp_get_num_threads(); i++) 
        #pragma omp ordered
        vec.insert(vec.end(), vec_private.begin(), vec_private.end());
    

这避免了为每个线程保存一个 std::vector,然后在并行区域之外将它们串行合并。我了解了这个“技巧”here。 我不确定如何为用户定义的减少执行此操作(或者是否可能)。。用户定义的缩减无法做到这一点。

我刚刚意识到关键部分不是必需的,我从这个问题parallel-cumulative-prefix-sums-in-openmp-communicating-values-between-thread 中发现了这一点。此方法也可以正确获取顺序

std::vector<int> vec;
size_t *prefix;
#pragma omp parallel

    int ithread  = omp_get_thread_num();
    int nthreads = omp_get_num_threads();
    #pragma omp single
    
        prefix = new size_t[nthreads+1];
        prefix[0] = 0;
    
    std::vector<int> vec_private;
    #pragma omp for schedule(static) nowait
    for(int i=0; i<100; i++) 
        vec_private.push_back(i);
    
    prefix[ithread+1] = vec_private.size();
    #pragma omp barrier
    #pragma omp single 
    
        for(int i=1; i<(nthreads+1); i++) prefix[i] += prefix[i-1];
        vec.resize(vec.size() + prefix[nthreads]);
    
    std::copy(vec_private.begin(), vec_private.end(), vec.begin() + prefix[ithread]);

delete[] prefix;

【讨论】:

至于最后一句中的问题:“对于任何reduction 子句,combiner 被执行的次数,以及这些执行的顺序是未指定的, 因此不可能。 谢谢,这对我很有帮助! 只是一个问题:如果我们有一个嵌套的 for 循环,上面的代码仍然可以写成:std::vector&lt;int&gt; vec; #pragma omp parallel #pragma omp for collapse(2) nowait schedule(static) for(int i=0; i&lt;N; i++) for(int j=0; j&lt; M; j++) #pragma omp for collapse(2) schedule(static) ordered for(int i=0; i&lt;omp_get_num_threads(); i++) #pragma omp ordered do some stuff 对丑陋的缩进感到抱歉 @Joachim,我不知道。我通常不使用嵌套并行。我也没有时间研究这个。也许问一个问题。这里有很多专家可以为您提供帮助。【参考方案2】:

您链接的问题是关于“在多个线程写入单个容器的情况下,STL 向量容器不是线程安全的”这一事实。只有当您调用可能导致std::vector 持有的底层数组重新分配的方法时,这才是正确的。 push_back()pop_back()insert() 是这些危险方法的示例。

如果您需要线程安全的重新分配,那么库 intel thread building block 为您提供 concurrent vector containers 。您不应在单线程程序中使用 tbb::concurrent_vector,因为访问随机元素所需的时间比 std::vector 执行相同操作所需的时间长(即 O(1))。但是,并发向量调用 push_back()pop_back()insert() 以线程安全的方式,即使发生重新分配也是如此。

编辑 1:the following Intel presentation 的幻灯片 46 和 47 给出了使用 tbb::concurrent_vector 并发重新分配的说明性示例

编辑 2:顺便说一下,如果您开始使用 Intel Tread Building Block(它是开源的,它适用于大多数编译器,并且与 C++/C++11 功能的集成比 openmp 好得多),那么您不需要不需要使用openmp来创建parallel_for,Here是使用tbb的parallel_for的一个很好的例子。

【讨论】:

以上是关于C++ OpenMP 并行 For 循环 - std::vector 的替代品 [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

使用 OpenMP 在 C、C++ 中并行化嵌套 for 循环的几种方法之间的区别

非for循环的OpenMP并行化

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

C++:OpenMP 并行循环内存泄漏

循环C ++中的分段错误Openmp

OpenMP 并行 for 循环异常