我正在尝试并行化基于粒子的模拟的代码,并且遇到基于OpenMP的方法的不良性能。我的意思是:
top
显示CPU使用率,运行CPU的OpenMP线程平均使用率为50%。以下伪代码说明了实现的所有并行区域的基本模板。
注意,在单个时间步骤期间,正在执行以下所示方式的5个平行区域。基本上,作用于粒子i < N
的力是相邻粒子j < NN(i)
的几个场属性的函数。
omp_set_num_threads(ncpu);
#pragma omp parallel shared( quite_a_large_amount_of_readonly_data, force )
{
int i,j,N,NN;
#pragma omp for
for( i=0; i<N; i++ ){ // Looping over all particles
for ( j=0; j<NN(i); j++ ){ // Nested loop over all neighbors of i
// No communtions between threads, atomic regions,
// barriers whatsoever.
force[i] += function(j);
}
}
}
我正在试图解决观察到的瓶颈的原因。我天真的初步猜测是为了解释:
如上所述,线程之间共享大量内存以进行只读访问。很可能不同的线程试图同时读取相同的内存位置。这是否会造成瓶颈?我是否应该让OpenMP分配私有副本?
答案 0 :(得分:2)
N
有多大,NN(i)
有多强?
您没有分享任何内容,但force[i]
可能位于force[i+1]
的同一缓存行中。这就是所谓的false sharing,可能非常有害。 OpenMP应该将事情一起批处理以弥补这一点,所以用足够大的N
我认为这不是你的问题。
如果NN(i)
不是CPU密集型的,那么您可能会遇到一个简单的内存瓶颈 - 在这种情况下,在其上放置更多内核将无法解决任何问题。
答案 1 :(得分:1)
假设force [i]是4或8字节数据的普通数组,那么你肯定会有错误的共享,毫无疑问。
假设函数(j)是独立计算的,你可能想要这样做:
for( i=0; i<N; i+=STEP ){ // Looping over all particles
for ( j=0; j<NN(i); j+=STEP ){ // Nested loop over all neighbors of i
// No communtions between threads, atomic regions,
// barriers whatsoever.
calc_next(i, j);
}
}
void calc_next(int i, int j)
{
int ii, jj;
for(ii = 0; ii < STEP; ii++)
{
for(jj = 0; jj < STEP; jj++)
{
force[i+ii] = function(j+jj);
}
}
}
这样,你在一个线程上计算了很多东西,在下一个线程上计算了很多东西,每一堆都远远不够,以至于你没有得到错误的共享。
如果您不能这样做,请尝试以其他方式将其拆分,这样每次都会计算出更大的部分。
答案 2 :(得分:0)
正如其他人所说,force
上的虚假分享可能是一个原因。试试这个简单的方法,
#pragma omp for
for( i=0; i<N; i++ ){
int sum = force[i];
for ( j=0; j<NN(i); j++ ){
sum += function(j);
}
force[i] = sum;
}
从技术上讲,force[i] = sum
可能仍会进行虚假共享。但是,它极不可能发生,因为另一个线程会访问force[i + N/omp_num_threads()*omp_thread_num()]
,这距离force[i]
很远。
如果仍然可扩展性差,请尝试使用英特尔Parallel Amplifier(或VTune)等分析器来查看每个线程需要多少内存带宽。如果是这样,在你的计算机中加入更多的DRAM :)这将真正提高内存带宽。