【问题标题】:Openmp nested parallelism use available threadsOpenmp 嵌套并行使用可用线程
【发布时间】:2025-12-23 12:40:11
【问题描述】:

所以,我有一个简单的 Fortran do 循环,并且在该循环内调用了几个子例程。我有 使 do 循环与 OpenMP 并行,像这样

    !$omp parallel do
    do i=1,n
        call a()
        call b()
    enddo
    !$omp end parallel do

现在大多数时候循环中的迭代次数是 与可用的处理器/线程的数量以及在内部调用的子例程相比更少 循环可以并行调用。那么,有没有办法在并行内部并行调用子例程 做循环?我试过像这样使用task

    !$omp parallel do
    do i=1,n
        !$omp task
        call a(i , j )
        !$omp end    task
        !$omp task
        call b(i, k)
        !$omp end task
        !$omp taskwait
    enddo
    !$omp end parallel do 

但这表明segmentation fault 存在一些错误。有什么办法可以做到这一点。

更新:

所以,我发现分段错误的主要原因来自 fftw 库。让我们考虑一个虚拟程序

program name
    !$use omp_lib
    implicit real*8(a-h,p-z)
    call system_clock(count_rate=irate)
    call system_clock(it1)
    !$ call omp_set_nested(.true.)
    !$omp parallel do 
    do i =1,5
        call test(i)
        print *, i
    enddo
    !$omp end parallel do 
    call system_clock(it2)
    print *, (it2-it1)/real(irate, kind=8)
end program name


subroutine test(ii)
    ! just a dummy subroutine for heavy computation
    implicit real*8(a-h,p-z)
        do j=1,40000
            !$omp task
            do k=1,40000
                x = exp(sqrt(sqrt(2.0d0*ii**3)**2))
            enddo
            !$omp end task
        enddo
end subroutine 

这个程序完全符合我的要求并使用任务指令,使用剩余的线程并提高性能。现在让我们考虑另一个虚拟程序,但使用 fftw,类似于我的工作。

program name
    !$use omp_lib
    implicit real*8(a-h,p-z)
    integer, parameter :: n=8192*8
    complex(kind=8) :: arr(n)
    real(kind=8) :: tmp1(n), tmp2(n)
    integer(kind=8) :: pF
    integer :: i

    call system_clock(count_rate=irate)
    call dfftw_plan_dft_1d(pF,n,arr,arr,-1,0) ! forward
    call system_clock(it1)

    !$ call omp_set_nested(.true.)
    !$omp parallel do private(arr)
    do i =1,5
        call random_number(tmp1)
        call random_number(tmp2)
        arr = cmplx(tmp1, tmp2, kind=8)
        call test(pF, arr)
        print *, i
    enddo
    !$omp end parallel do 
    call system_clock(it2)

    print *, (it2-it1)/real(irate, kind=8)

end program name


subroutine test(pF, arr)
    implicit real*8(a-h,p-z)
    complex(kind=8) :: arr(:)
    integer(kind=8) :: pF
    do j=1,100
        !$omp task private(arr)
        do k=1, 100
            call dfftw_execute_dft(pF, arr, arr)
        enddo
        !$omp end task
    enddo
end subroutine

现在,这会引发分段错误。 (注意:我的实际程序中没有随机数字调用,它们在这里只是为了一个虚拟目的)。我检查了http://www.fftw.org/fftw3_doc/Thread-safety.htmlfftw_execute 是线程安全的,并且程序在没有task 指令的情况下工作。但是使用 task 它会引发错误。有谁知道如何解决这个问题?

【问题讨论】:

  • 我能找到的所有示例都涉及任务子句周围的$omp parallel$omp single。你试过吗?我会先让它在没有外部并行循环的情况下工作。完成后,添加回外部循环并在 OpenMP 中启用嵌套并行,它应该可以按预期工作。
  • "some error with segmentation fault" 如果你对错误的解释感兴趣,你必须展示更多,否则我们可以告诉你,你有 一些代码中的问题。您是否启用了嵌套并行性?如何?任务还不够吗?为什么不完全删除parallel do
  • 我已经用新的细节更新了问题,请检查。
  • 请修正隐式 real*8 和 kind=8,它们是对现代 Fortran 实践的厌恶。它在哪里出现故障?为什么你的任务有 private(arr)?

标签: fortran openmp fftw


【解决方案1】:

唉,又一个例子说明了为什么 !$omp do parallel 是个坏主意……我确实认为最好将线程创建和工作共享阶段明确分开。

正如 Vladimir 在 cmets 中所说,您没有提供足够的详细信息来说明您遇到分段错误的原因。不过,您似乎对 OpenMP 有一些误解,我可以尝试解决。

首先,实现您想要的并避免任何额外的 OpenMP 指令的一种非常快速而肮脏的方法是

!$omp parallel default( none ) private( i ) shared( n ) ! Create threads
!$omp do                                                ! Now share out the work
Do i = 1, 2 * n
   If( Mod( i, 2 ) == 1 ) Then
     Call a
   Else
     Call b
End Do
!$omp end do
!$omp end parallel

但是,如果您想使用任务,如果对 a 和 b 的所有调用都是完全独立的,那么您可能不是最简单的方法。在这种情况下,请记住,只要任何线程遇到!$omp task,就会创建一个新任务,并且该任务可以由任何线程执行,而不仅仅是创建它的线程。按照这样的逻辑

!$omp parallel default( none ) private( i ) shared( n ) ! Crate the threads
!$omp single
Do i = 1, n
   !$omp task
   Call a
   !$omp end task
   !$omp task
   call b
   !$omp end task
end do
!$omp end single
!$omp end parallel

是你想要的——你使用一个线程来创建任务列表,然后(或者更可能在创建列表时)所有可用线程将执行它们,每个任务都由下一个可用线程执行。请注意,我也错过了 taskwait 指令,因为从您的描述中我不确定您为什么认为您需要它,因为我认为此时不需要同步。

【讨论】:

  • 我已经用新的细节更新了问题,请检查。