控制并行循环中的线程数&减少开销

时间:2016-12-22 00:04:01

标签: loops parallel-processing fortran 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

一些嵌套的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

我的第一个想法是,我不知道如何正确设置线程数。

问题:

  1. 我是如何构建并行代码的?
  2. 有没有更好的方法来实施我已经/想要做的事情?

2 个答案:

答案 0 :(得分:2)

确实存在一些显而易见的错误:您已从代码中删除了任何并行性。在创建最外面的并行区域之前,您将其大小定义为一个线程。因此,只会创建一个单独的线程来处理该区域内的任何代码。随后使用omp_set_num_threads(4)不会改变它。此调用仅表示下一个parallel指令将创建4个线程(除非另有明确请求)。但是没有这样的新parallel指令,它会在当前的嵌套中。您只有一个工作共享do指令,该指令适用于一个唯一线程的当前封闭parallel区域。

有两种方法可以解决您的问题:

  1. 保持您的代码原样:虽然正式,您将在parallel区域的进入和退出时分叉并加入您的线程,OpenMP标准不会请求创建线程并且被毁坏了实际上,它甚至鼓励线程保持活动以减少parallel指令的开销,这是由大多数OpenMP运行时库完成的。因此,这种问题的简单方法的有效载荷并不太大。

  2. 使用第二种方法将parallel指令推到最外层循环之外,但创建了你需要的工作共享线程(我相信4)。然后,使用parallel指令将single区域内的任何顺序包含在内。这将确保不会发生与额外线程的不必要的交互(退出时隐式屏障和共享变量的刷新),同时避免您不想要的并行性。

  3. 最后一个版本看起来像这样:

    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)

  1. 没有显然错误但如果串行循环需要很长时间,那么您的加速将会受到限制。进行并行计算可能需要重新设计算法。
  2. 使用!$omp master - !$omp end master指令将执行减少到单个线程,而不是设置循环中的线程数。如果只有在完成所有其他线程后才能运行此块,则添加!$omp barrier