OpenMP线程之间的矢量填充

Vector filling across OpenMP threads

本文关键字:填充 线程 之间 OpenMP      更新时间:2023-10-16

我有一个算法,其中一个目标是填充向量。为了提高性能,算法的迭代分布在OpenMP线程中。我想知道哪种方式可以提供更好/更安全的矢量填充方式。

注意,向量的排序必须一致(即vec1的值n必须来自与vec2的值n相同的迭代。)

假设1:

std::vector<BasicType> vec1;
std::vector<BasicType> vec2;
#pragma opm parallel for
for(...)
{
    // Do some intensive stuff to compute val1 and val2
    // ...
    #pragma omp critical
    {
        vec1.push_back(val1);
        vec2.push_back(val2);
    }
}
// Then go on to work with vec1 and vec2...

假设2:

std::vector<BasicType> vec1;
std::vector<BasicType> vec2;
#pragma opm parallel
{
    std::vector<BasicType> vec1local;
    std::vector<BasicType> vec2local;
    #pragma omp for
    for(...)
    {
        // Do some intensive stuff to compute val1 and val2
        // ...
        vec1local.push_back(val1);
        vec2local.push_back(val2);
    }
    #pragma omp critical
    {
        // See note 1 below
        vec1.insert(vec1.end(), vec1local.begin(), vec1local.end());
        vec2.insert(vec2.end(), vec2local.begin(), vec2local.end());
    }
}
// Then go on to work with vec1 and vec2...

注1:这取自将矢量附加到矢量的最佳方式

注意2:似乎val1和val2可以在某个对象中组合,以保持一致性,并且只适用于一个向量,但就目前而言,对于算法的其余部分来说,这似乎是不切实际的。

注意3:为了给出一个数量级,for循环包含大约100次迭代,分为4个线程。除了极少数例外,每个迭代都应该有相同的工作负载(这会导致关键部分在同一时间发生的问题)

注意4:仅供记录,整个过程涉及图像稳定,并在Tegra K1架构上运行。使用的编译器是gcc 4.8.4。

从你的两个建议中,我更喜欢第一个。它更简单,不需要额外的内存。然而,我建议一种没有critical部分的替代方案:

std::vector<BasicType> vec1(size);
std::vector<BasicType> vec2(size);
#pragma opm parallel for
for(...)
{
    // Do some intensive stuff to compute val1 and val2
    // ...
    vec1[i] = val1;
    vec2[i] = val2;
}

请注意,这可能会由于缓存线盗用而导致一些性能问题。然而,我不会担心这一点,除非这是一个通过实际性能分析验证的实际问题。解决方案可能是:

  • 使用第二个解决方案。(这需要花费内存和额外的后处理)
  • 对齐你的向量,并为循环使用适当的块。(这样每个线程都有本地缓存行)
  • 使用内部包含局部矢量,但外部提供必要的全局矢量运算的数据结构。(总的来说,这可能是最好的解决方案。)

我假设您需要使用向量,而不能使用数组(尽管您的问题不是很有趣)。使用t = omp_get_num_threads(),您可以并行填充矢量,然后将它们合并到log2(t)操作中,而不是像以下那样的t操作中

void reduce(std::vector<BasicType> *v1, std::vector<BasicType> *v2, int begin, int end) {
    if(end - begin == 1) return;
    int pivot = (begin+end)/2;
    #pragma omp task
    reduce(v, begin, pivot);
    #pragma omp task
    reduce(v, pivot, end);
    #pragma omp taskwait
    v1[begin].insert(v1[begin].end(), v1[pivot].begin(), v1[pivot].end());
    v2[begin].insert(v2[begin].end(), v2[pivot].begin(), v2[pivot].end());
}

std::vector<BasicType> v1, v2;
std::vector<BasicType> *v1p, *v2p;
#pragma omp parallel
{
    #pragma omp single
    {
        v1p = new std::vector<BasicType>[omp_get_num_threads()];
        v2p = new std::vector<BasicType>[omp_get_num_threads()];
    }
    #pragma omp for
    for(...) {
        // Do some intensive stuff to compute val1 and val2
        // ...
       v1p[omp_get_thread_num()].push_back(val1);
       v2p[omp_get_thread_num()].push_back(val2);
    }
    #pragma omp single
    reduce(v1p, v2p, 0, omp_get_num_threads());
}
v1 = v1p[0], v2 = v2p[0];
delete[] v1p;
delete[] v2p;

例如,对于8个线程,这将加入线程的向量

(0,1) (2,3) (4,5) (6,7)
(0,2) (4,6)
(0,4)

有关并行填充矢量的更多信息,请参阅。有关在log2(t)操作中合并线程的更多信息,请参阅此问题的答案。