21

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

4

2 回答 2

52

我认为您可以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. 上面的代码可以简化为

#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,然后在并行区域之外将它们串行合并。我在这里了解了这个“技巧” 。我不确定如何为用户定义的减少执行此操作(或者是否可能)。. 使用用户定义的缩减是不可能做到这一点的。

我刚刚意识到关键部分不是必需的,我从这个问题中发现了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;
于 2013-09-07T08:11:37.997 回答
16

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

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

编辑 1:以下英特尔演示文稿的幻灯片 46 和 47给出了使用 tbb::concurrent_vector 并发重新分配的说明性示例

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

于 2013-09-07T06:24:46.073 回答