何时应该使用DO CONCURRENT,何时应该使用OpenMP?

6

我知道这个这个,但是我再次询问,因为第一个链接已经很旧了,第二个链接似乎没有得出确定的答案。是否有任何共识发展?

我的问题很简单:

我有一个DO循环,其中的元素可能被并发运行。我应该使用哪种方法?

下面是在简单立方晶格上生成粒子的代码。

  • npart是粒子数量
  • npart_edgenpart_face分别沿边缘和面
  • space是晶格间距
  • RxRyRz是位置数组
  • xyz是用于确定晶格上位置的临时变量

请注意,当使用CONCURRENT的情况下,x,y和z必须是数组,但在OpenMP案例中不需要,因为它们可以定义为PRIVATE。

所以我应该使用DO CONCURRENT(根据上面的链接,它使用SIMD):

DO CONCURRENT (i = 1, npart)
    x(i) = MODULO(i-1, npart_edge)
    Rx(i) = space*x(i)
    y(i) = MODULO( ( (i-1) / npart_edge ), npart_edge)
    Ry(i) = space*y(i)
    z(i) = (i-1) / npart_face
    Rz(i) = space*z(i)
END DO

我应该使用OpenMP吗?
!$OMP PARALLEL DEFAULT(SHARED) PRIVATE(x,y,z)
!$OMP DO
DO i = 1, npart
    x = MODULO(i-1, npart_edge)
    Rx(i) = space*x
    y = MODULO( ( (i-1) / npart_edge ), npart_edge)
    Ry(i) = space*y
    z = (i-1) / npart_face
    Rz(i) = space*z
END DO
!$OMP END DO
!$OMP END PARALLEL

我的测试:

将64个粒子放置在边长为10的盒子中:

$ ifort -qopenmp -real-size 64 omp.f90
$ ./a.out 
CPU time =  6.870000000000001E-003
Real time =  3.600000000000000E-003

$ ifort -real-size 64 concurrent.f90 
$ ./a.out 
CPU time =  6.699999999999979E-005
Real time =  0.000000000000000E+000

在一个边长为100的盒子中放置100000个粒子:
$ ifort -qopenmp -real-size 64 omp.f90
$ ./a.out 
CPU time =  8.213300000000000E-002
Real time =  1.280000000000000E-002

$ ifort -real-size 64 concurrent.f90 
$ ./a.out 
CPU time =  2.385000000000000E-003
Real time =  2.400000000000000E-003

使用DO CONCURRENT结构似乎能够提高至少一个数量级的性能。这是在i7-4790K上完成的。此外,并发的优势似乎随着规模的增加而减少。

1
关于在DO CONCURRENT情况下x、y和z需要是数组的断言并不是语言要求。DO CONCURRENT的实现也很大程度上取决于编译器的能力——懒惰的编译器可能只将其实现为普通的串行循环,而没有任何向量化或并行化。所以答案是......“这取决于情况。” - IanH
1
@IanH 当您说它不是语言要求时,您的意思是什么?我认为它们必须是数组,否则这些操作无法同时进行。此外,作为编辑,我已添加了关于性能的信息。 - physkets
1
那么现在的问题是什么?是DO CONCURRENT和OpenMP的比较吗?还是如何让这段代码运行得更快?这两个问题是非常不同的。一般来说,答案就是使用更快的方法。 - Vladimir F Героям слава
1
@IanH 好的,我已经阅读并理解了。所以它的意思就是处理器被允许以任意顺序执行迭代,这可能是并发的,也可能不是。这真的是一个糟糕的命名情况。 - physkets
2
仅仅因为我们有一个闪亮的新功能:do concurrent的一些文档 - francescalus
显示剩余7条评论
1个回答

7

DO CONCURRENT本身并不进行任何并行化。编译器可以决定使用线程并行化或使用SIMD指令,甚至将其卸载到GPU。对于线程,您通常需要指示它这样做。对于GPU卸载,您需要具有特定选项的特定编译器。或者(通常情况下!),编译器只是将DO CONCURENT视为常规DO,并在常规DO使用SIMD时使用它们。

OpenMP也不仅限于线程,编译器可以使用SIMD指令。还有omp simd指令,但那只是建议编译器使用SIMD,它可能会被忽略。

您应该尝试、测量和查看。没有单一的确定性答案。即使对于给定的编译器,也不是所有编译器都适用。

如果您无论如何都不使用OpenMP,我建议您尝试使用DO CONCURRENT,以查看自动并行处理器是否能更好地处理此结构。很有可能会有帮助。如果您的代码已经在OpenMP中,则我认为引入DO CONCURRENT没有任何意义。

我的做法是使用OpenMP,并尝试确保编译器对它可以进行向量化(SIMD)。特别是因为我已经在整个程序中都使用了OpenMP。DO CONCURRENT仍然需要证明它是否真正有用。我还不太确定,但有些GPU示例看起来很有前途 - 然而,真实代码通常要复杂得多。

您的具体示例和性能测量:

给出的代码太少,每个基准测试都有微妙的点。我围绕您的循环编写了一些简单的代码,并进行了自己的测试。我小心地将线程创建排除在计时块之外。您不应该将$omp parallel包括在计时中。我还取多次计算中的最小实际时间,因为有时第一次需要更长时间(特别是使用DO CONCURRENT)。CPU具有各种节流模式,可能需要一些时间来加速。我还添加了SCHEDULE(STATIC)

npart=10000000
ifort -O3 concurrent.f90: 6.117300000000000E-002
ifort -O3 concurrent.f90 -parallel: 5.044600000000000E-002
ifort -O3 concurrent_omp.f90: 2.419600000000000E-002

npart=10000,默认8个线程(超线程)
ifort -O3 concurrent.f90: 5.430000000000000E-004
ifort -O3 concurrent.f90 -parallel: 8.899999999999999E-005
ifort -O3 concurrent_omp.f90: 1.890000000000000E-004

npart=10000OMP_NUM_THREADS=4(忽略超线程)
ifort -O3 concurrent.f90: 5.410000000000000E-004
ifort -O3 concurrent.f90 -parallel: 9.200000000000000E-005
ifort -O3 concurrent_omp.f90: 1.070000000000000E-004

在这里,使用DO CONCURRENT似乎对于小型案例而言要快一些,但如果我们确保使用正确数量的核心,则差别不大。对于大型案例,它显然更慢。使用-parallel选项对于自动并行化是必要的。


我已将您早些时候请求的信息作为编辑添加。另外,我在我的代码中其他地方确实使用了openmp,但在那里,并发不会有所帮助。 - physkets
猜测你是在启用超线程的情况下运行,你需要检查并行性能是否比默认线程数少,设置OMP_PLACES=cores,然后再得出并行效率太低的结论。没有什么理由期望do concurrent自动并行与OpenMP表现不同。 - tim18
@tim18 最好在问题下面发表评论。此答案是在详细情况揭示前编写的,完全不反映它们。 - Vladimir F Героям слава
@tim18 所以如果我在DO循环内放置一个BLOCK,每次迭代的本地变量是独立的吗?这比创建一个数组更高效吗?另外,为什么我应该设置omp不使用超线程? - physkets
@physkets 为什么不使用超线程?因为它经常会减慢代码的运行速度。如果你有4个核心,让它使用8个超线程并不能神奇地带来新的核心。这是一个复杂的话题,请不要在这里讨论。如果你想了解更多,请提出一个完整的问题。如果我删除我的回答,所有这些评论都将消失。 - Vladimir F Героям слава
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接