这个OpenMP障碍有什么解决方法吗?

时间:2017-05-23 08:39:22

标签: multithreading parallel-processing synchronization openmp barrier

我用OpenMp编写了这个并行区域:

std::vector<T> sharedResult;
#pragma omp parallel
{
std::vector<T> result;
#pragma omp for nowait
for(int i=0; i<n; i++){
  //fill result
}
#pragma omp critical{
  sharedResult.insert(sharedResult.end(), result.begin(), result.end());
}
#pramga omp barrier
#pragma omp for nowait
for(size_t i=0; i<sharedResult.size(); i++){
  foo(sharedResult[i]);
}
...
}

我担心#pragma omp barrier是必要的。我认为的原因是,当一个线程击中最后一个#pragma omp for时,此时sharedResult.size()仍未处于最终状态(在前一个并行完成时获得)。请注意,遗憾的是sharedResult的大小以前是未知的。

不幸的是,我注意到这个障碍产生了很大的开销,即一个特定的迭代比所有其他迭代更昂贵,因此所有线程都必须等待执行该迭代的线程。这可以被视为负载不平衡,但我没有找到任何解决方案来解决这个问题。

所以我的问题是:有没有办法启动最后一个并行而不等待前一个并行完成或者严重无法改善这个?

1 个答案:

答案 0 :(得分:3)

我同意这个障碍是必要的。我看到了几种方法,复杂性越来越高,效率越来越高:

任务

为每个结果元素发布任务:

#pragma omp parallel
{
    std::vector<T> result;
    #pragma omp for nowait
    for(int i=0; i<n; i++){
        //fill result
    }
    // would prefer a range based loop here, but
    // there seem to be issues with passing references 
    // to tasks in certain compilers
    for(size_t i=0; i<result.size(); i++){
    {
        #pragma omp task
        foo(result[i]);
    }
}

您甚至可以在初始循环中发布任务。如果任务太多,可能会产生很大的开销。

使用完成的线程处理结果队列

现在这个比较棘手 - 特别是你需要区分结果队列空和完成第一个循环的所有线程。

std::vector<T> sharedResult;
int threadsBusy;
size_t resultIndex = 0;
#pragma omp parallel
{
    #pragma omp single
    threadsBusy = omp_num_threads();

    std::vector<T> result;
    #pragma omp for nowait
    for(int i=0; i<n; i++){
        //fill result
    }

    #pragma omp critical
    {
        sharedResult.insert(sharedResult.end(), result.begin(), result.end());
        threadsBusy--;
    }

    do {
        bool hasResult, allThreadsDone;
        // We need a copy here as the vector may be resized
        // and elements may become invalid by insertion
        T myResult;
        #pragma omp critical
        {
            if (resultIndex < sharedResult.size()) {
                resultIndex++;
                hasResult = true;
                myResult = sharedResult[myResult];
            } else {
                hasResult = false;
            }
            allThreadsDone = threadsBusy == 0;
        }
        if (hasResult) {
            foo(myResult);
        } else {
            if (allThreadsDone) {
                break;
            }
            // If we just continue here, we will spin on the mutex
            // Unfortunately there are no condition variables in OpenMP
            // So instead we go for a quick nap as a compromise
            // Feel free to tune this accordingly
            std::this_thread::sleep_for(10ms);
        }
    } while (true);
}

注意:通常我会测试我在此处发布的代码,但由于缺乏完整的示例,我无法进行测试。

处理通过并行循环生成块

最后,对于已经完成的结果,您可以多次为并行for循环运行。然而,这有许多问题。首先,所有线程必须遇到每个工作共享区域,即使是那些迟到的第一个线程也是如此。所以你必须跟踪你运行的循环。每个线程的循环绑定也必须相同 - 而且您只能在关键部分中读取sharedResult.size()。所以你必须事先通过一个关键部分的一个线程读取共享变量,但是等待所有线程直到它被正确读取。此外,您必须使用动态调度,否则您可能会使用静态调度,并且您将等待最后完成的线程。您编辑的示例中没有这些内容。我不会理所当然地认为for nowait schedule(dynamic)可以在团队中的所有线程进入之前完成(但它适用于libgomp)。考虑到所有事情,我不会真的去那里。