如何并行化嵌套循环

时间:2020-06-08 05:18:44

标签: fortran mpi openmp openmpi

下面显示了一个与我的代码具有相同结构的小示例串行代码。

PROGRAM MAIN
IMPLICIT NONE
INTEGER          :: i, j, k
DOUBLE PRECISION :: en,ei,es
DOUBLE PRECISION :: ki(1000,2000), et(200),kn(2000)
OPEN(UNIT=3, FILE='output.dat', STATUS='UNKNOWN')
DO i = 1, 1000, 1
   DO j = 1, 2000, 1
      ki(j,k) = DBLE(j) + DBLE(k)
   END DO
END DO
DO i = 1, 200, 1
   en = 2.0d0/DBLE(200)*(i-1)-1.0d0
   et(i) = en
   es = 0.0d0
   DO j = 1, 1000, 1
      kn=ki(j,:)
      CALL CAL(en,kn,ei)
      es = es + ei
   END DO
   WRITE (UNIT=3, FMT=*) et(i), es
END DO
CLOSE(UNIT=3)
STOP
END PROGRAM MAIN

SUBROUTINE CAL (en,kn,ei)
IMPLICIT NONE
INTEGER          :: i
DOUBLE PRECISION :: en, ei, gf,p
DOUBLE PRECISION :: kn(2000)
p = 3.14d0
ei = 0.0d0
DO i = 1, 2000, 1
   gf = 1.0d0 / (en - kn(i) * p)
   ei = ei + gf
END DO
RETURN
END SUBROUTINE CAL

我正在集群中运行我的代码,该集群在一个节点上有32个CPU,一个节点上的32个CPU共享总共250 GB的内存。我最多可以使用32个节点。

每次完成内部循环时,都会收集一个数据。完成所有外部循环后,总共要收集200个数据。如果只有一个CPU执行内部循环,则将花费3天以上(超过72小时)。

我想分别对内部循环和外部循环进行并行化?有人可以建议如何并行化此代码吗?

我可以分别对内部循环和外部循环使用MPI技术吗?如果是这样,如何区分执行不同循环(内部循环和外部循环)的不同CPU?

另一方面,我看到有人提到混合MPI和OpenMP方法的并行化。是否可以将MPI技术用于外部循环,将OpenMP技术用于内部循环?如果是这样,那么每次完成每个内部循环后如何收集一个数据到CPU,而在所有外部循环完成后如何收集总共200个数据到CPU。如何区分分别执行内部循环和外部循环的不同CPU?

或者,有人可以提出其他关于并行化代码并提高效率的建议吗?预先非常感谢。

1 个答案:

答案 0 :(得分:2)

正如评论中提到的,一个好的答案需要更详细的问题。然而,乍一看似乎并行化内部循环

DO j = 1, 1000, 1
  kn=ki(j,:)
  CALL CAL(en,kn,ei)
  es = es + ei
END DO

应该足以解决您的问题,或者至少它会是一个很好的开始。首先我猜循环上有错误

DO i = 1, 1000, 1
  DO j = 1, 2000, 1
    ki(j,k) = DBLE(j) + DBLE(k)
  END DO
END Do

因为 k 设置为 0 并且没有地址对应于 0 的单元格(请参阅您的变量声明)。 ki 也被声明为 ki(1000,2000) 数组,而 ki(j,i) 是 (2000,1000) 数组。除了这些错误,我想 ki 应该计算为

ki(i,j) = DBLE(j) + DBLE(i)

如果为真,我建议您使用以下解决方案

PROGRAM MAIN
IMPLICIT NONE
INTEGER          :: i, j, k,icr,icr0,icr1
DOUBLE PRECISION :: en,ei,es,timerRate
DOUBLE PRECISION :: ki(1000,2000), et(200),kn(2000)
INTEGER,PARAMETER:: nthreads=1
call system_clock(count_rate=icr)
timerRate=real(icr)
call system_clock(icr0)
call omp_set_num_threads(nthreads)
OPEN(UNIT=3, FILE='output.dat', STATUS='UNKNOWN')
DO i = 1, 1000, 1
  DO j = 1, 2000, 1
    ki(i,j) = DBLE(j) + DBLE(i)
  END DO
END DO

DO i = 1, 200, 1
  en = 2.0d0/DBLE(200)*(i-1)-1.0d0
  et(i) = en
  es = 0.0d0
  !$OMP PARALLEL DO private(j,kn,ei) firstpribate(en) shared(ki) reduction(+:es)
  DO j = 1, 1000, 1
    kn=ki(j,:)
    CALL CAL(en,kn,ei)
    es = es + ei
  END DO
  !$OMP END PARALLEL DO 
  WRITE (UNIT=3, FMT=*) et(i), es
END DO
CLOSE(UNIT=3)
call system_clock(icr1)
write (*,*) (icr1-icr0)/timerRate ! return computing time 
STOP

END PROGRAM MAIN

SUBROUTINE CAL (en,kn,ei)
IMPLICIT NONE
INTEGER          :: i
DOUBLE PRECISION :: en, ei, gf,p
DOUBLE PRECISION :: kn(2000)
p = 3.14d0
ei = 0.0d0

DO i = 1, 2000, 1
  gf = 1.0d0 / (en - kn(i) * p)
  ei = ei + gf
END DO

RETURN
END SUBROUTINE CAL

我添加了一些变量来检查计算时间;-)

此解在 5.14 秒内计算,nthreads=1,2.75 秒,nthreads=2。它没有将计算时间除以 2,但对于第一次拍摄似乎很划算。不幸的是,在这台机器上我有一个核心 i3 proc。所以我不能比 nthreads=2 做得更好。但是,我想知道,当 nthreads=16 时,代码会如何表现???

请告诉我

希望对您有所帮助。

最后,我警告在实际代码中可能会仔细考虑变量状态(私有、第一私有和共享)的选择。