我正在学习如何使用OpenMP使代码使用多个处理器。最近,我尝试使用OpenMP使埃瓦尔德求和傅立叶部分并行。下面是名为PME_fourier_oprimized的函数,我尝试使用OpenMP杂注使其并行,其中,
ParticleN = Number of cahrged particles in the System (an integer number)
kcount = an integer number
qi = prop[i][0] (a double float) which gives charge of a particle i
Ext_L[0] = x dimension of the system (a double float number)
Ext_L[1] = y dimension of the system (a double float number)
Ext_L[1] = z dimension of the system (a double float number)
**r_pos = ParticleNx3 array (r_pos[i][0], r_pos[i][1], and r_pos[i][2] = x, y , and z postions of a particle i)
**PME_mev_ksq = kcountx4 array
**f_kewald = ParticleNx3 array which stores the forces
double PME_Fourier_optimized(int kcount, double **r_pos, double **PME_mvec_ksq, double **prop, double **f_kewald, double *Ext_L){
// Auto alpha determination //
double TRTF = 50*5.5;
double box_VOL = 8*Ext_L[0]*Ext_L[1]*Ext_L[2]; // calculate the volume of the box //
alpha = pow((ParticleN*M_PI*M_PI*M_PI*TRTF)/(box_VOL*box_VOL),1./6.); // Ewald cutoff parameter //
double GAMMA = -0.25/(alpha*alpha);
double recip = 2*M_PI*ONE_PI_EPS0/(8*Ext_L[0]*Ext_L[1]*Ext_L[2]);
double e_kewald=0.0;
double fx_kewald[ParticleN];
double fy_kewald[ParticleN];
double fz_kewald[ParticleN];
#pragma omp parallel for
for (int i=0;i<ParticleN;i++){
fx_kewald[i] = 0.0;
fy_kewald[i] = 0.0;
fz_kewald[i] = 0.0;
for (int j=0;j<dimen;j++){
f_kewald[i][j] = 0.0;
}
}
#pragma omp parallel for reduction(+:e_kewald,fx_kewald[:ParticleN],fy_kewald[:ParticleN],fz_kewald[:ParticleN])
for(int k=0; k<kcount; k++){
double ak_cos=0.0;
double ak_sin=0.0;
double mx = PME_mvec_ksq[k][0];
double my = PME_mvec_ksq[k][1];
double mz = PME_mvec_ksq[k][2];
double ksq = PME_mvec_ksq[k][3];
#pragma omp parallel for reduction(+:ak_sin,ak_cos)
for(int i=0;i<ParticleN;i++){
double qi = prop[i][0]; // charge of particle i //
double kdotr = mx*r_pos[i][0] + my*r_pos[i][1] + mz*r_pos[i][2];
ak_sin -= qi*sin(kdotr);
ak_cos += qi*cos(kdotr);
}
double a = ak_cos;
double b = ak_sin;
double akak = (a*a + b*b);
double tmp = recip * exp(GAMMA*ksq)/ksq;
#pragma omp parallel for reduction (+:fx_kewald[:ParticleN],fy_kewald[:ParticleN],fz_kewald[:ParticleN])
for(int i=0;i<ParticleN;i++){
double qi = prop[i][0];
double kdotr = mx*r_pos[i][0] + my*r_pos[i][1] + mz*r_pos[i][2];
double tmp2 = 2*tmp*qi*(sin(kdotr) * a + cos(kdotr) * b);
//Edit3: Following @Laci advice to use 1D array to store the forces and using the reduction to gain a huge acceleration compared to only using omp critical (See the comment section for more details).
fx_kewald[i] += tmp2 * mx;
fy_kewald[i] += tmp2 * my;
fz_kewald[i] += tmp2 * mz;
}
e_kewald += tmp * akak; // add the energies for each k //
}
//Edit3: Finally storing the calculated values in the 2D array //
#pragma omp parallel for
for(int i=0;i<ParticleN;i++){
f_kewald[i][0] = fx_kewald[i];
f_kewald[i][1] = fy_kewald[i];
f_kewald[i][2] = fz_kewald[i];
}
//printf("PME KEwald: %lf\n", e_kewald);
return e_kewald;
}
我将这个代码输出与我的串行代码版本进行了比较,注意到e_kewald能量和f_kewald力在小数点后2位有错误。有趣的是,每次运行它都得到不同的结果(可能是数据竞争的情况)。
当我删除外层k循环之前的第一个pragma命令(#pragma omp parallel for private(k,mx,my,mz,ksq,qi,kdotr,tmp2)reduction(+:e_kewald))时,它开始完美地工作并与串行代码结果完美匹配。我是否做错了什么?我也不确定我使用pargma的方式是否正确。任何帮助都是非常感谢的。
Edit1:在f_kewald数组解决问题之前使用#pragma omp critical之后。但是,不确定这是否是最好的方法。
我的2d数组是使用以下函数定义的:
double **alloc_2d_double(int rows, int cols){
int i=0;
double *data = (double *)malloc(rows*cols*sizeof(double));
double **array= (double **)malloc(rows*sizeof(double *));
for (i=0; i<rows; i++)
array[i] = &(data[cols*i]);
return array;
}
我仍然想知道f_kewald [i][0],f_kewald [i][1],f_kewald [i][2]是否可以在约简下使用。
Edit2:声明循环中的变量,并在omp临界区中放置大括号。
微粒数量~5000 - 10000千个~2000
Edit3:使用带约简的1D数组(fx_kewald,fy_kewald,fz_kewald)来提高并行化速度,而不是使用带2D数组的opm临界区。(感谢@Laci下面的评论)
1条答案
按热度按时间yhxst69z1#
代码的简化视图:
很明显,不同的线程会同时更新
f_kewald[i][0]
,从而导致错误,所有i
都会发生这种情况。当你把所有更新
f_kewald
的代码放到一个临界区时,这会有所帮助--现在线程会互相等待,但是更新的顺序仍然是任意的,临界区不会区分i
的不同值。我建议你只在一个层次上并行你的计算--决定你是想在外部循环还是内部循环做,而不是两个都做。假设你的
ParticleN
足够大,你不需要并行你的外部循环。这是最简单的情况。如果你的
ParticleN
可以很小,最好并行化你的外循环,但是你必须设法解决f_kewald
的同步问题。这是一个糟糕的情况-你必须调整你的代码,这可能是困难的或不可能的。如果你可以避免它(见上文),请这样做!
不管怎样,很多人都建议不要使用
private
来控制线程的变量分配。这是个好建议!你应该在正确的C作用域中声明变量,而不是使用private
。这将使你的代码转换更容易(如果你需要的话),并将防止不愉快的意外(当你忘记了巨大的private
列表中的一个变量时)。