控制并行循环中的线程数并减少开销
Posted
技术标签:
【中文标题】控制并行循环中的线程数并减少开销【英文标题】:Controlling Number of Threads in Parallel Loops & Reducing Overhead 【发布时间】:2017-05-07 12:47:01 【问题描述】:在我的 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 a = 1, 50
,我比较了主 DO 循环每次迭代的运行速度,结果如下:
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
我的第一个想法是我没有正确设置线程数。
问题:
-
我构建并行代码的方式是否有明显问题?
有没有更好的方法来实现我已经完成/想要做的事情?
【问题讨论】:
您已将并行设置始终设置为 1 个线程。 你能具体说明一下我是在哪里做的吗? 【参考方案1】:确实有一些明显错误的地方:您已经从代码中删除了任何并行性。在创建最外层并行区域之前,您将其大小定义为一个线程。因此,将只创建一个线程来处理该区域内的任何代码。随后使用 omp_set_num_threads(4)
不会改变这一点。这个调用只是说无论下一个 parallel
指令将创建 4 个线程(除非另有明确要求)。但是没有这样的新parallel
指令,它会在当前指令中嵌套。您只有一个工作共享 do
指令,该指令应用于一个唯一线程的当前封闭 parallel
区域。
有两种方法可以解决您的问题:
保持您的代码原样:尽管形式上,您将在进入和退出 parallel
区域时分叉并加入您的线程,但 OpenMP 标准不要求创建和销毁线程。实际上,它甚至鼓励线程保持活动状态以减少 parallel
指令的开销,这是由大多数 OpenMP 运行时库完成的。因此,这种简单的问题处理方法的payload并不会太大。
使用第二种方法将 parallel
指令推送到最外层循环之外,但创建工作共享所需的尽可能多的线程(我相信这里有 4 个)。然后,使用single
指令将必须在parallel
区域内连续的任何内容包含在内。这将确保不会发生与额外线程的不必要交互(隐式屏障和退出时刷新共享变量),同时避免您不想要的并行性。
最后一个版本如下所示:
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 是永远的。
【讨论】:
好吧,这是有道理的。我的印象是我可以更改下一个“OMP 语句”的线程数,但显然这仅指 !$OMP PARALLEL 而不仅仅是 !$OMP DO。现在很明显,我创建了一个只有一个工作线程的并行区域,谢谢!【参考方案2】:-
显然没有错,但如果串行循环需要很长时间,您的加速将受到限制。进行并行计算可能需要重新设计算法。
不要设置循环中的线程数,而是使用
!$omp master
- !$omp end master
指令将执行减少到单个线程。如果您只能在所有其他线程完成后运行此块,请添加 !$omp barrier
。
【讨论】:
所以添加 !$OMP MASTER // !$OMP END MASTER 代码的任一侧我只想运行一次?如果我这样做并删除第一个 set_num_threads(1),那么它会为每个线程启动主 DO 循环,然后崩溃。以上是关于控制并行循环中的线程数并减少开销的主要内容,如果未能解决你的问题,请参考以下文章