OpenMP 并行的两种方法之间的区别
Difference between two methods for OpenMP parallel
我只想通过 OpenMP 的汇总来评估函数的集成,方法是使用一个数组来保存每个步骤中计算的每个值 > 对所有值求和;并在没有数组的情况下求和。
密码是:
double f(double x)
{
return sin(x)*sin(x)/(x*x+1);
}
方法一
long i = 0;
const long NUM_STEP = 100000;
double sum[NUM_STEP];
double from = 0.0, to = 1.0;
double step = (to - from)/NUM_STEP;
double result = 0;
#pragma omp parallel for shared(sum) num_threads(4)
for(i=0; i<NUM_STEP; i++)
sum[i] = step*f(from+i*step);
for(i=0; i<NUM_STEP; i++)
result += sum[i];
printf("%lf", result);
方法 2
long i = 0;
const long NUM_STEP = 100000;
double from = 0.0, to = 1.0;
double step = (to - from)/NUM_STEP;
double result = 0;
#pragma omp parallel for shared(result) num_threads(4)
for(i=0; i<NUM_STEP; i++)
result += step*f(from+i*step);
printf("%lf", result);
但是结果相差太大了。 METHOD 1 给出了一个稳定的值,而 METHOD 2 给出了一个可变的值。这是一个例子:
方法一:0.178446
方法 2:0.158738
METHOD 1 的值是正确的(由另一个工具检查)。
TL;DR 第一种方法没有 race-condition 而第二种方法有。
第一种方法没有竞争条件,而第二种方法有。即,在第一种方法中:
#pragma omp parallel for shared(sum) num_threads(4)
for(i=0; i<NUM_STEP; i++)
sum[i] = step*f(from+i*step);
for(i=0; i<NUM_STEP; i++)
result += sum[i];
每个线程将运算结果step*f(from+i*step);
保存在数组sum[i]
的不同位置。而之后master线程,依次递减数组sum
上保存的值,即:
for(i=0; i<NUM_STEP; i++)
result += sum[i];
实际上,您可以在此版本上进行一些改进;与其分配与 NUM_STEP
的数量相同大小的数组 sum
,您可以只分配与线程数量相同的大小,并且每个线程将保存在等于它的位置ID
,即:
int total_threads = 4;
double sum[total_threads];
#pragma omp parallel num_threads(total_threads)
{
int thread_id = omp_get_thread_num();
for(i=0; i<NUM_STEP; i++)
sum[thread_id] += step*f(from+i*step);
for(i=0; i< total_threads; i++)
result += sum[i];
}
尽管如此,最好的方法是实际修复第二种方法。
在第二种方法中,在更新变量 result
:
时存在 race-condition
#pragma omp parallel for shared(result) num_threads(4)
for(i=0; i<NUM_STEP; i++)
result += step*f(from+i*step);
因为 result
变量正在以非线程安全方式由多个线程同时更新。
要解决这个 race-condition 你需要使用 reduction 子句:
#pragma omp parallel for reduction(+:result) num_threads(4)
for(i=0; i<NUM_STEP; i++)
result += step*f(from+i*step);
我只想通过 OpenMP 的汇总来评估函数的集成,方法是使用一个数组来保存每个步骤中计算的每个值 > 对所有值求和;并在没有数组的情况下求和。
密码是:
double f(double x)
{
return sin(x)*sin(x)/(x*x+1);
}
方法一
long i = 0;
const long NUM_STEP = 100000;
double sum[NUM_STEP];
double from = 0.0, to = 1.0;
double step = (to - from)/NUM_STEP;
double result = 0;
#pragma omp parallel for shared(sum) num_threads(4)
for(i=0; i<NUM_STEP; i++)
sum[i] = step*f(from+i*step);
for(i=0; i<NUM_STEP; i++)
result += sum[i];
printf("%lf", result);
方法 2
long i = 0;
const long NUM_STEP = 100000;
double from = 0.0, to = 1.0;
double step = (to - from)/NUM_STEP;
double result = 0;
#pragma omp parallel for shared(result) num_threads(4)
for(i=0; i<NUM_STEP; i++)
result += step*f(from+i*step);
printf("%lf", result);
但是结果相差太大了。 METHOD 1 给出了一个稳定的值,而 METHOD 2 给出了一个可变的值。这是一个例子:
方法一:0.178446
方法 2:0.158738
METHOD 1 的值是正确的(由另一个工具检查)。
TL;DR 第一种方法没有 race-condition 而第二种方法有。
第一种方法没有竞争条件,而第二种方法有。即,在第一种方法中:
#pragma omp parallel for shared(sum) num_threads(4)
for(i=0; i<NUM_STEP; i++)
sum[i] = step*f(from+i*step);
for(i=0; i<NUM_STEP; i++)
result += sum[i];
每个线程将运算结果step*f(from+i*step);
保存在数组sum[i]
的不同位置。而之后master线程,依次递减数组sum
上保存的值,即:
for(i=0; i<NUM_STEP; i++)
result += sum[i];
实际上,您可以在此版本上进行一些改进;与其分配与 NUM_STEP
的数量相同大小的数组 sum
,您可以只分配与线程数量相同的大小,并且每个线程将保存在等于它的位置ID
,即:
int total_threads = 4;
double sum[total_threads];
#pragma omp parallel num_threads(total_threads)
{
int thread_id = omp_get_thread_num();
for(i=0; i<NUM_STEP; i++)
sum[thread_id] += step*f(from+i*step);
for(i=0; i< total_threads; i++)
result += sum[i];
}
尽管如此,最好的方法是实际修复第二种方法。
在第二种方法中,在更新变量 result
:
#pragma omp parallel for shared(result) num_threads(4)
for(i=0; i<NUM_STEP; i++)
result += step*f(from+i*step);
因为 result
变量正在以非线程安全方式由多个线程同时更新。
要解决这个 race-condition 你需要使用 reduction 子句:
#pragma omp parallel for reduction(+:result) num_threads(4)
for(i=0; i<NUM_STEP; i++)
result += step*f(from+i*step);