OMP SECTIONS和DO在同一个PARALLEL块中

时间:2016-06-21 16:22:57

标签: multithreading fortran openmp

我有3个完全独立的任务,因此是并行执行的理想选择:

任务1:执行名为subA()的(单线程)子程序。

任务2:执行名为subB()的(单线程)子程序。

任务3:在DO循环中填充数组。 DO循环的每次迭代都独立于所有其他迭代。

假设我有8个线程。我想线程0在任务1上工作,线程1在任务2上工作,线程2-7在任务3上工作。在Fortran中,我想象这样的事情:

COMPLEX*8, EXTERNAL :: func

!$OMP PARALLEL

!$OMP SECTIONS
!$OMP SECTION
  !
  ! Task 1, performed by one thread
  !
  CALL subA()
!$OMP SECTION
  !
  ! Task 2, performed by one thread
  !
  CALL subB()
!$OMP END SECTIONS NOWAIT

!$OMP DO
  !
  ! Task 3, performed by all threads
  !
  DO j=1,nn             
    vals(j) = func(j)
  END DO
!$OMP END DO NOWAIT

!$OMP END PARALLEL

但上面的代码并不是我想要的。在任务1和2上工作的线程也被安排在任务3中的DO循环上工作,这似乎会减慢一切,可能是因为那两个线程“迟到”到达DO循环,因此所有其他线程必须在PARALLEL区域末尾的隐式屏障处等待它们。

在这种情况下处理线程调度的正确方法是什么?

冒着提供太多信息的风险,我已经知道subA()和subB()是计算密集型的,而func(j)的每个评估都相对较快。对于每个subA()和subB()来说,完成它需要大致相同的时间,因为当多个线程被分配给后一个任务时,整个DO循环就完成了。

一些注意事项:

  1. 很有可能将NUM_THREADS(N)子句添加到“OMP PARALLEL”并将NUM_THREADS(N-2)添加到“OMP DO”。但我认为“OMP DO”构造不接受NUM_THREADS条款。
  2. 一种解决方案是在DO循环上使用DYNAMIC调度。这样,并行DO循环将在完成后拾取执行subA()和subB()的线程。这是有效的,但是不满意,因为DYNAMIC调度增加了大量的开销,因此增加了DO循环的执行时间令人不快的数量。
  3. 此处显示相关问题:put multiple do-s and section-s in the same parallel environment。但是,该问题的答案只表明可以将SECTIONS和DO与相同的PARALLEL环境结合起来,但它没有解决我在这里提出的调度问题。
  4. 我最初在英特尔的开发人员论坛here上提出了这个问题,并建议我交叉发布到stackoverflow。
  5. ETA:来自英特尔的人指出我原来的问题含糊不清:我不清楚我的DO循环是否只是一个从func(1..nn)到vals(1..nn)的memcpy(),或者我是不是正在调用一个名为func()的函数nn次。后者是我的意图,我在示例代码中澄清了这一点。

2 个答案:

答案 0 :(得分:0)

使用OpenMP任务,类似这样(未经测试)

!$omp parallel
! Have a single thread create all the tasks. 
!$omp single
!$omp task
   call subA()
!$omp end task
!$omp task
   call subB()
!$omp end task
   DO j=1,nn    
!$omp task         
      vals(j) = func(j)
!$omp end task
   END DO
!$omp end single
!$omp end parallel

答案 1 :(得分:0)

我到目前为止找到的最佳解决方案是手动执行我希望OpenMP会自动为我执行的调度。特别是,让nthr成为我想要使用的线程数。然后我按如下方式使用SELECT CASE结构:

SELECT CASE (nthr)
CASE (3)
  ! Use 3 threads. See example below
CASE (4)
  ! Use 4 threads. See example below
CASE (5)
  ! And so on...
CASE DEFAULT
  ! Catch-all
END SELECT

如果我有4个线程,我会执行以下操作:

!$OMP PARALLEL PRIVATE(j) NUM_THREADS(4)
!$OMP SECTIONS
!$OMP SECTION
  !
  ! Task 1, performed by one thread
  !
  CALL subA()

!$OMP SECTION
  !
  ! Task 2, performed by one thread
  !
  CALL subB()

!$OMP SECTION
  !
  ! One thread does half the loop...
  !
  DO j=1,nn/2
    vals(j)=func(j)
  END DO

!$OMP SECTION
  !
  ! ...and one thread does the other half
  !
  DO j=nn/2+1,nn
    vals(j)=func(j)
  END DO

!$OMP END SECTIONS NOWAIT
!$OMP END PARALLEL

nthr的其他值的情况是对上述代码的明显修改。

此解决方案在正确且快速的意义上运行良好,但从软件工程的角度来看,代码重复使其不是最理想的。