Ser*_*pos 0 c time for-loop openmp
我正在解决一个比较串行、mpi 和 openMP 代码之间的执行时间的问题。问题是openMP版本比mpi慢。有没有办法让下面的 openMP 代码比 mpi 更快?
for(i=0;i<loop;i++)
{
#pragma omp parallel for private(k,dx,dy,dz,d,a) schedule(dynamic)
for(j=0;j<N;j++)
{
for(k=0;k<N;k++)
{
if(j!=k)
{
dx=C[k*3+0]-C[j*3+0];
dy=C[k*3+1]-C[j*3+1];
dz=C[k*3+2]-C[j*3+2];
d=sqrt(pow(dx,2)+pow(dy,2)+pow(dz,2));
F[j*3+0]-=G*M[j]*M[k]/pow(d,3)*dx;
F[j*3+1]-=G*M[j]*M[k]/pow(d,3)*dy;
F[j*3+2]-=G*M[j]*M[k]/pow(d,3)*dz;
}
}
}
#pragma omp for schedule(dynamic)
for(j=0;j<N;j++)
{
for(k=0;k<3;k++)
{
a=F[j*3+k]/M[j];
V[j*3+k]=V[j*3+k]+a*Dt[i];
C[j*3+k]=C[j*3+k]+V[j*3+k]*Dt[i];
}
}
}
Run Code Online (Sandbox Code Playgroud)
这段代码的作用是,外循环是该过程将要发生的次数,并且Dt最终也在表中使用。第二个循环描述移动的质量,第三个循环计算系统中存在的其他质量推动它的力。之后的两个循环计算新的位置。考虑到这一点,我无法移动外循环中的并行性,因为在每个i循环中都需要一个新的更新C表。那么是否有什么需要更改的地方,以便此代码可以运行得更快。
有关该问题的更多信息
loop:取值在 10.000 - 1,000,000,000 之间(由用户提供)N:取 2 - 10 之间的值(由用户提供)C:取min和之间的随机值max(由用户提供)F和V:初始值 0.00G:6.673e-11桌子的分配
M=malloc(N*sizeof(int));
C=malloc(N*3*sizeof(float));
F=malloc(N*3*sizeof(float));
V=malloc(N*3*sizeof(float));
Dt=malloc(loop*sizeof(float));
Run Code Online (Sandbox Code Playgroud)
表值
for(i=0;i<N;i++)
{
M[i]=rand()%(high-low+1)+low;
}
for(i=0;i<N*3;i++)
{
C[i]=rand()%(max-min+1)+min;
F[i]=0.0;
V[i]=0.0;
}
for(i=0;i<loop;i++)
{
Dt[i]=(float)rand()/RAND_MAX;
}
Run Code Online (Sandbox Code Playgroud)
您可以从替换为schedule(dynamic)开始schedule(static)。这里绝对不需要动态调度,因为每次迭代完成的工作量是恒定的。schedule(dynamic)默认为 的块大小1,并动态地将每次迭代分配给某个具有相关巨大开销的线程。
当每次迭代涉及不同的工作量时,动态调度非常有用,在这种情况下,静态调度可能会导致负载不平衡和线程空闲。典型案例正在为分形集着色。即使如此,通常更合理的做法是分派多个迭代的块中的工作项,以最大限度地减少分派开销。
第二个循环不是并行运行的,因为您拥有的是孤立的 OpenMPfor构造,而不是组合的parallel for构造。您还需要制作k和a私有。
现在我们知道它N确实很小并且loop具有如此大的值,可以采取一些措施来提高性能。
首先,不存在“调用omp parallel一次启动并行性”这样的事情。有一些并行区域,每当流控制通过它们时就会并行执行。并行区域是遵循 OpenMP 构造的代码块parallel。OpenMP 工作共享构造,例如for仅在并行区域的动态范围内并行执行。因此,第二个循环不是并行的,因为它只是一个for构造,并且没有按词法或动态嵌套在parallel区域中。
为了使术语更加清晰,并行区域内 OpenMP 构造的词法嵌套意味着:
#pragma omp parallel
{
#pragma omp for
for (...) {}
}
Run Code Online (Sandbox Code Playgroud)
动态嵌套意味着:
foo() {
#pragma omp for
for (...) {}
}
#pragma omp parallel
{
foo();
}
Run Code Online (Sandbox Code Playgroud)
foo()仅从并行区域外部调用不会使循环并行运行。
有一些速记组合结构,例如parallel for当并行区域主体中的唯一代码是工作共享结构(例如for.
其次,并行区域不是免费的。OpenMP 遵循并行计算的 fork/join 模型,其中程序按顺序执行,直到执行流遇到并行区域。当这种情况发生时,工作线程会出现分叉,并且程序开始并行执行。在并行区域的末尾,工作线程重新连接回主线程,程序继续按顺序执行。分叉和加入在执行时间方面是有代价的。尽管几乎所有现代 OpenMP 运行时都使用线程池,并且由于操作系统生成所有工作线程所需的时间,只有第一个并行区域激活确实很慢,但 fork/join 开销仍然不可忽略。因此,除非有足够的工作可以在线程之间分配,以便可以分摊开销,否则使用 OpenMP 是没有意义的。
这是问题的一个说明。四次迭代,每次占用一个时间单位,使用两个线程顺序并行计算。fork 和 join 的开销都是两个时间单位:
| sequential parallel
| +------------+ +-------------------------+
| | it.0 | | fork |
| | it.1 | | overhead |
| | it.2 | | it.0 | it.2 |
| | it.3 | | it.1 | it.3 |
| +------------+ | join |
| | overhead |
| +-------------------------+
v time
Run Code Online (Sandbox Code Playgroud)
尽管在两个线程之间划分迭代可以使计算速度提高两倍,但开销使并行版本总体上变慢。
相同,但现在有十次迭代:
| sequential parallel
| +------------+ +-------------------------+
| | it.0 | | fork |
| | it.1 | | overhead |
| | it.2 | | it.0 | it.5 |
| | it.3 | | it.1 | it.6 |
| | it.4 | | it.2 | it.7 |
| | it.5 | | it.3 | it.8 |
| | it.6 | | it.4 | it.9 |
| | it.7 | | join |
| | it.8 | | overhead |
| | it.9 | +-------------------------+
| +------------+
|
v time
Run Code Online (Sandbox Code Playgroud)
显然,并行版本现在更快,并且迭代次数越多,速度会更快,从低于 2 倍的加速逐渐接近。请注意,这里的问题不是第一种情况下只有四次迭代,而是这些迭代每次只花费一个时间单位。对于迭代次数较少但每次迭代计算时间较多的问题,可以使用 OpenMP。
如果并行区域位于具有多次迭代的外循环内部(这正是您的情况),第一种情况下的问题可能会大大加剧。规范的解决方案是将外循环移到并行区域内。这样,将有一个分叉和一个连接,并且开销不会被复制。使用您的代码,如下所示:
| sequential parallel
| +------------+ +-------------------------+
| | it.0 | | fork |
| | it.1 | | overhead |
| | it.2 | | it.0 | it.2 |
| | it.3 | | it.1 | it.3 |
| +------------+ | join |
| | overhead |
| +-------------------------+
v time
Run Code Online (Sandbox Code Playgroud)
现在你必须非常小心,因为整个循环都在并行区域内,并且每个线程都在执行所有迭代,即没有迭代分布。没有应用于i循环的工作共享指令,因此i必须明确给出private处理。更好的编码风格应该在并行区域内声明所有私有变量,在这种情况下,根本不需要子句private,但出于演示原因,这里没有这样做。
由于i循环迭代并不是彼此独立的,因此您必须确保所有线程都以锁步方式执行这些迭代。这通常是通过屏障同步来实现的,在上面的代码中,屏障同步来自构造末尾的隐式屏障for。这同样适用于迭代内的不同阶段。同样,由于后者末尾的隐式屏障,第二个工作共享构造不会在前一个工作共享构造完成之前启动。