C语言中的OpenMP共享阵列:还原与原子更新问题

我无法确定此OpenmP任务示例出了什么问题。就上下文而言,y是一个大型共享数组,并且rowIndex对于每个任务都不是唯一的。可能有多个任务试图增加值y [rowIndex]。

我的问题是,y是否需要由归约子句保护,还是原子更新就足够了?我目前正遇到一个更大的程序崩溃,并且想知道我是否正在对此进行基本设置。

在我看到的示例中,由于每个线程的数组复制,大多数数组减少都是针对非常小的数组,而大多数原子更新未在数组元素上使用。似乎没有太多内容可以一次在一个竞争条件下更新一个元素的共享数组(而且基于任务的并行性的情况很少见。)

#pragma omp parallel shared(y) // ??? reduction(+:y) ???
#pragma omp single
for(i = 0; i < n; i++)
{  
  sum = DoSmallWork_SingleThread();  
  rowIndex = getRowIndex_SingleThread();

  #pragma omp task firstprivate(sum,rowIndex)  
  {    
    sum += DoLotsofWork_TaskThread();

    // ??? #pragma omp atomic update ???
    y[ rowIndex ] += sum;  
  }
}
yychuan123456 回答:C语言中的OpenMP共享阵列:还原与原子更新问题

基本上,您有3种解决方案来避免这类竞争条件,您都提到了。它们都工作得很明显:

  1. 原子访问,即让线程/任务在同一时刻访问同一阵列,但确保正确的操作顺序,这是通过使用shared子句带有atomic子句的数组:

    #pragma omp parallel
    #pragma omp single
    for(i = 0; i < n; i++)
    {
        sum = DoSmallWork_SingleThread();
        rowIndex = getRowIndex_SingleThread();
    
        #pragma omp task firstprivate(sum,rowIndex) shared(y)
        {
            increment = sum + DoLotsOfWork_TaskThread();
    
            #pragma omp atomic
            y[rowIndex] += increment;
        }
    }
    
  2. 私有化,即每个任务/线程都有其自己的数组副本,然后将它们汇总在一起,这就是reduction子句的作用:>

    #pragma omp parallel
    #pragma omp single
    #pragma omp taskgroup task_reduction (+:y[0:n-1])
    for(int i = 0; i < n; i++)
    {
        int sum = DoSmallWork_SingleThread();
        int rowIndex = getRowIndex_SingleThread();
    
        #pragma omp task firstprivate(sum,rowIndex) in_reduction(+:y[0:n-1])
        {
            y[rowIndex] += sum + DoLotsOfWork_TaskThread();
        }
    }
    
  3. 对阵列或阵列部分的专有访问,这是用于任务相关性的(例如,您可以为基于线程的并行性模型使用互斥体来实现) :

    #pragma omp parallel
    #pragma omp single
    for(i = 0; i < n; i++)
    {
        sum = DoSmallWork_SingleThread();
        rowIndex = getRowIndex_SingleThread();
    
        #pragma omp task firstprivate(sum,rowIndex) depend(inout:y[rowIndex])
        {
            y[rowIndex] += sum + DoLotsOfWork_TaskThread();
        }
    }
    

什么时候应该使用它们?

  1. 原子访问是一种较慢的内存访问类型,可提供一致性保证,在冲突(即两个(或多个)线程尝试同时修改同一值的情况)下尤其慢。

  2. >

    最好仅在对y的更新很少且相距很远且发生冲突的可能性较低的情况下使用原子。

  3. 私有化通过制作阵列的副本并将它们(在您的情况下,将它们添加在一起)复制在一起来避免冲突问题。

    y的大小成比例,这会导致内存开销,并可能影响缓存。

  4. 最后,提供任务依赖性可以通过使用 scheduling 来完全避免该问题,即仅同时运行修改数组各个部分的任务。通常,当y较大且修改y的操作在任务中很频繁时,这是更可取的。

    但是,您的并行性受到定义的依赖项数量的限制,因此在上例中,受y中的行数的限制。例如,如果您只有8行但有32个内核,那么这可能不是最佳方法,因为您只会使用25%的计算能力。

NB:这意味着在私有化(aka缩减)情况下,尤其是在依赖关系情况下,您可以受益于将数组y的各个部分组合在一起,通常是让任务在多个连续的行上进行操作。然后,您可以减少(分别减少,或增加依赖关系的大小)任务子句中提供的数组块的大小。

本文链接:https://www.f2er.com/3164467.html

大家都在问