con*_*l_c 5 parallel-processing fortran loops openmp gfortran
在我的Fortran 95代码中,我有一系列嵌套的DO循环,整个过程需要大量的时间来计算,所以我想用OpenMP添加并行功能(gfortran -fopenmp用于编译/构建).
有一个主要的DO循环,运行1000次.
其中有一个子DO循环,运行100次.
其他几个DO循环嵌套在此中,迭代次数随着DO循环的每次迭代而增加(第一次一次,最后一次最多1000次).
例:
DO a = 1, 1000
DO b = 1, 100
DO c = 1, d
some calculations
END DO
DO c = 1, d
some calculations
END DO
DO c = 1, d
some calculations
END DO
END DO
d = d + 1
END DO
Run Code Online (Sandbox Code Playgroud)
一些嵌套的DO循环必须以串行方式运行,因为它们本身包含依赖关系(也就是说,循环的每次迭代都有一个包含上一次迭代的值的计算),并且在这种情况下不能轻易并行化.
我可以轻松地使没有任何依赖项的循环并行运行,如下所示:
d = 1
DO a = 1, 1000
DO b = 1, 100
DO c = 1, d
some calculations with dependencies
END DO
!$OMP PARALLEL
!$OMP DO
DO c = 1, d
some calculations without dependencies
END DO
!$OMP END DO
!$OMP END PARALLEL
DO c = 1, d
some calculations with dependencies
END DO
END DO
d = d + 1
END DO
Run Code Online (Sandbox Code Playgroud)
但是我知道打开和关闭并行线程会有很大的开销,因为这在循环中会发生很多次.代码运行速度明显慢于以前按顺序运行时的速度.
在此之后,我认为在主循环的任一侧打开和关闭并行代码是有意义的(因此只应用一次开销),并将线程数设置为1或8以控制部分是按顺序运行还是并行,如下:
d = 1
CALL omp_set_num_threads(1)
!$OMP PARALLEL
DO a = 1, 1000
DO b = 1, 100
DO c = 1, d
some calculations with dependencies
END DO
CALL omp_set_num_threads(4)
!$OMP DO
DO c = 1, d
some calculations without dependencies
END DO
!$OMP END DO
CALL omp_set_num_threads(1)
DO c = 1, d
some calculations with dependencies
END DO
END DO
d = d + 1
END DO
!$OMP END PARALLEL
Run Code Online (Sandbox Code Playgroud)
但是,当我设置它运行时,我没有得到运行并行代码所期望的加速.我希望前几个开销能够较慢,但是过了一段时间我希望并行代码比顺序代码运行得更快,但事实并非如此.我比较了主DO循环的每次迭代的运行速度DO a = 1, 50,结果如下:
Iteration Serial Parallel
1 3.8125 4.0781
2 5.5781 5.9843
3 7.4375 7.9218
4 9.2656 9.7500
...
48 89.0625 94.9531
49 91.0937 97.3281
50 92.6406 99.6093
Run Code Online (Sandbox Code Playgroud)
我的第一个想法是我不知何故没有正确设置线程数.
问题:
确实存在一些明显错误的地方:您已从代码中删除了所有并行性。在创建最外层并行区域之前,您将其大小定义为一个线程的大小。因此,只会创建一个线程来处理该区域内的任何代码。随后使用omp_set_num_threads(4)不会改变这一点。这个调用仅仅表示无论下一个parallel指令将创建 4 个线程(除非明确要求)。但没有这样的新parallel指令,它本来可以嵌套在当前指令中。您只有一个do应用于parallel一个唯一线程的当前封闭区域的工作共享指令。
有两种方法可以解决您的问题:
保持代码不变:尽管形式上,您将在进入和退出该parallel区域时分叉和加入线程,但 OpenMP 标准并不要求创建和销毁线程。实际上,它甚至鼓励线程保持活动状态以减少指令的开销parallel,大多数 OpenMP 运行时库都这样做。因此,这种简单方法解决问题的有效负载并不算太大。
使用第二种方法将parallel指令推到最外层循环之外,但创建工作共享所需的尽可能多的线程(我相信这里有 4 个线程)。parallel然后,您可以使用指令将您所在区域内必须连续的内容括起来single。这将确保不会发生与额外线程的不需要的交互(隐式屏障和退出时刷新共享变量),同时避免您不想要的并行性。
最后一个版本看起来像这样:
d = 1
!$omp parallel num_threads( 4 ) private( a, b, c ) firstprivate( d )
do a = 1, 1000
do b = 1, 100
!$omp single
do c = 1, d
some calculations with dependencies
end do
!$omp end single
!$omp do
do c = 1, d
some calculations without dependencies
end do
!$omp end do
!$omp single
do c = 1, d
some calculations with dependencies
end do
!$omp end single
end do
d = d + 1
end do
!$omp end parallel
Run Code Online (Sandbox Code Playgroud)
现在这个版本是否真的比原始版本更快,这取决于你的测试。
最后一点是:由于代码中有相当多的连续部分,因此无论如何都不要期望有太多的加速。阿姆达尔定律是永恒的。