假设我们要在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);
}
因此功能较少。
为什么我会使用减少而不是原子访问计数器?
答案 0 :(得分:10)
性能
因为原子变量带有价格,所以这个价格是同步的。 为了确保不存在争用条件,即两个线程同时修改同一变量,线程必须同步,这实际上意味着您失去了并行性,即线程被序列化。
另一方面,归约是可以使用并行归约算法并行并行执行的常规操作。 阅读this和this文章以获取有关并行约简算法的更多信息。
想象一下您有4
个线程并且想要减少8
元素数组A的情况。您可以分3个步骤执行此操作(请检查所附图像以更好地了解什么我在说):
i<4
的线程负责对A[i]=A[i]+A[i+4]
求和的结果。i<2
的线程负责对A[i]=A[i]+A[i+4/2]
求和的结果。i<4/4
的线程负责对A[i]=A[i]+A[i+4/4]
求和的结果在此过程结束时,您将得到A
的第一个元素即A[0]
答案 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个周期,如果大量使用,将大大降低性能。