有两种版本的openmp代码可以减少和不减少。
//减少
#pragma omp parallel for reduction(+:sum)
for (i=1;i<= num_steps; i++){
x = (i-0.5)*step;
sum = sum + 4.0/(1.0+x*x);
}
//不减少
#pragma omp parallel private(i)
{
int id = omp_get_thread_num();
int numthreads = omp_get_num_threads();
double x;
double partial_sum = 0;
for (i=id;i< num_steps; i+=numthreads){
x = (i+0.5)*step;
partial_sum += + 4.0/(1.0+x*x);
}
#pragma omp critical
sum += partial_sum;
}
我使用8个核心运行代码,缩减版本的总时间加倍。什么原因?感谢。
答案 0 :(得分:0)
如果你想手动并行化循环以及减少你可以这样做:
#pragma omp parallel private(i)
{
int id = omp_get_thread_num();
int numthreads = omp_get_num_threads();
int start = id*num_steps/numthreads;
int finish = (id+1)*num_steps/numthreads;
double x;
double partial_sum = 0;
for (i=start; i<finish ; i++){
x = (i+0.5)*step;
partial_sum += + 4.0/(1.0+x*x);
}
#pragma omp atomic
sum += partial_sum;
}
但是,我不建议这样做。减少不必使用原子进行,您应该让OpenMP并行化循环。第一种情况是最好的解决方案(但请确保声明x私有)。
编辑根据Hristo的说法,一旦你将x私有化,这两种方法的速度几乎相同。我想解释一下为什么在第二种方法中使用critical而不是原子或允许OpenMP进行减少对于这种情况下的性能几乎没有任何影响。
我可以通过两种方式考虑减少:
第一次演员阵容的核心数量呈线性收敛。第二种情况是核心数的对数。因此,我很容易认为第二种情况总是更好。然而,对于仅八个核心,减少完全取决于取部分和。添加8个原子/关键数字而不是3个步骤减少树数将是可以忽略不计的。
如果您有例如1024个核心?然后树只能在10个步骤中减少,线性总和需要1024步。但是对于第二种情况,并且对大数组进行部分求和,常数项可以大得多,例如100万元素可能仍然主导着减少。
所以我怀疑使用原子甚至关键还原对缩短时间的影响可以忽略不计。
答案 1 :(得分:0)
OpenMP中的标量减少通常非常快。在你的案例中观察到的行为是由于两件事以两种不同的方式出错。
在您的第一个代码中,您没有将x
设为私有。因此,它在线程之间共享,除了得到不正确的结果之外,执行还会受到数据共享的影响。每当一个线程写入x
时,它执行的核心就会向所有其他核心发送一条消息,并使它们使该缓存行的副本无效。当其中任何一个稍后写入x
时,必须重新加载整个缓存行,然后所有其他核心中的缓存行都将失效。等等。这会大大减慢速度。
在您的第二个代码中,您使用了OpenMP critical
构造。与原子添加相比,这是相对较重的,通常用于在最后实现减少。 x86上的Atomic add使用LOCK
指令前缀执行,所有内容都在硬件中实现。另一方面,关键部分是使用互斥锁实现的,需要多个指令并且通常需要忙等待循环。这远远低于原子添加的效率。
最后,由于数据共享条件不佳,您的第一个代码速度会变慢。由于使用了错误的同步原语,您的第二个代码速度变慢。只是在您的特定系统上,后一种效果不如前者那么严重,因此您的第二个示例运行得更快。