在我的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
一些嵌套的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
但是我知道打开和关闭并行线程会产生很大的开销,因为这会在循环中发生很多次。代码运行速度明显慢于以前按顺序运行时的速度。
在此之后,我认为在主循环的任一侧打开和关闭并行代码是有意义的(因此只应用一次开销),并将线程数设置为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
然而,当我将其设置为运行时,我没有获得运行并行代码所期望的加速。我希望前几个会考虑到开销的速度较慢,但过了一段时间我希望并行代码的运行速度比顺序代码要快,但事实并非如此。我比较了主要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
我的第一个想法是,我不知道如何正确设置线程数。
问题:
答案 0 :(得分:2)
确实存在一些显而易见的错误:您已从代码中删除了任何并行性。在创建最外面的并行区域之前,您将其大小定义为一个线程。因此,只会创建一个单独的线程来处理该区域内的任何代码。随后使用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
现在,与天真的相比,这个版本实际上是否会更快,您可以自行测试。
最后一句话:由于你的代码中有很多连续的部分,所以不要期望加速太快。 Amdahl's law是永远的。
答案 1 :(得分:1)
!$omp master
- !$omp end master
指令将执行减少到单个线程,而不是设置循环中的线程数。如果只有在完成所有其他线程后才能运行此块,则添加!$omp barrier
。