为什么要使用归约变量而不是原子变量?

时间:2019-01-14 17:17:55

标签: c parallel-processing openmp atomic

假设我们要在OpenMP循环中计数。比较减少量

int counter = 0;
#pragma omp for reduction( + : counter )
for (...) {
    ...
    counter++;
}

原子增量

int counter = 0;
#pragma omp for
for (...) {
    ...
    #pragma omp atomic
    counter++
}

原子访问立即提供结果,而归约仅在循环结束时假定其正确值。例如,减少不允许这样做:

int t = counter;
if (t % 1000 == 0) {
    printf ("%dk iterations\n", t/1000);
}

因此功能较少。

为什么我会使用减少而不是原子访问计数器?

2 个答案:

答案 0 :(得分:10)

简短答案:

性能

长答案:

因为原子变量带有价格,所以这个价格是同步的。 为了确保不存在争用条件,即两个线程同时修改同一变量,线程必须同步,这实际上意味着您失去了并行性,即线程被序列化

另一方面,

归约是可以使用并行归约算法并行并行执行的常规操作。 阅读thisthis文章以获取有关并行约简算法的更多信息。


附录:了解并行约简的工作原理

想象一下您有4个线程并且想要减少8元素数组A的情况。您可以分3个步骤执行此操作(请检查所附图像以更好地了解什么我在说):

  • 步骤0 。索引为i<4的线程负责对A[i]=A[i]+A[i+4]求和的结果。
  • 步骤1 。索引为i<2的线程负责对A[i]=A[i]+A[i+4/2]求和的结果。
  • 第2步。索引为i<4/4的线程负责对A[i]=A[i]+A[i+4/4]求和的结果

在此过程结束时,您将得到A的第一个元素即A[0]

减少的结果。

enter image description here

答案 1 :(得分:4)

性能是关键。

考虑以下程序

#include <stdio.h>
#include <omp.h>
#define N 1000000
int a[N], sum;

int main(){
  double begin, end;

  begin=omp_get_wtime();
  for(int i =0; i<N; i++)
    sum+=a[i];
  end=omp_get_wtime();
  printf("serial %g\t",end-begin);

  begin=omp_get_wtime();
# pragma omp parallel for
  for(int i =0; i<N; i++)
# pragma omp atomic
    sum+=a[i];
  end=omp_get_wtime();
  printf("atomic %g\t",end-begin);

  begin=omp_get_wtime();
# pragma omp parallel for reduction(+:sum)
  for(int i =0; i<N; i++)
    sum+=a[i];
  end=omp_get_wtime();
  printf("reduction %g\n",end-begin);
}

执行时(gcc -O3 -fopenmp),它给出:

序列0.00491182原子0.0786559减少0.001103

因此,大约atomic = 20xserial = 80xreduction

“精简”适当地利用了并行性,并且使用4核计算机,与“串行”相比,我们可以获得3--6的性能提升。

现在,“原子”比“序列”长20倍。不仅如前一个答案所述,内存访问的序列化会禁用并行性,而且所有内存访问都由原子操作完成。在现代计算机上,这些操作至少需要20--50个周期,如果大量使用,将大大降低性能。