【问题标题】:How to parallelize the nested loop如何并行化嵌套循环
【发布时间】:2020-09-27 00:42:31
【问题描述】:

一个与我的代码结构相同的小示例串行代码如下所示。

PROGRAM MAIN
IMPLICIT NONE
INTEGER          :: i, j
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(i,j) = DBLE(i) + DBLE(j)
   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 执行内层 Loop,则需要 3 天以上(72 小时以上)。

我想分别对内循环和外循环进行并行化?有人能建议如何并行化这段代码吗?

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

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

或者,有人会提供任何其他关于并行化代码和提高效率的建议吗?非常感谢您。

【问题讨论】:

  • 恐怕要很好地回答这个问题确实需要更多细节。混合 MPI+OpenMP 可能是实现此目的的好方法,但可以肯定地说,您需要提供更多详细信息,尤其是关于内存使用和数据依赖关系的信息,以及说明您正在尝试实现的目标的最小示例将非常有帮助。
  • 请注意,MPI 将要求您重写整个循环,甚至可能重写整个代码,因为它需要在每个处理器上使用不同的 j 开始和结束值。您是否尝试过任何编译器开关?
  • @Ian Bush,高性能标记和wander95 非常感谢您的回复。如果我正在运行我的代码,我已经用一个小的示例序列代码和集群信息修改了我的帖子。如果您能为并行化提供任何解决方案,我将不胜感激。还是请您使用混合 MPI 和 OpenMP 方法修改这个小型串行示例代码?再次感谢您。
  • 感谢您的示例。如果它被重新打开,我会试着找时间来回答。但是在您考虑并行性之前,我应该指出的一件事是,由于您以错误的顺序访问 ki 的元素,串行性能会很差 - 您应该真正尝试编写代码,以便您在第一时间移动最快的索引一个,不是最后一个。因此,在并行性之前,我建议您重写代码以处理 ki 转置,而不是像上面写的那样。
  • 我不会通过电子邮件进行 - 我会尽力提供帮助,但我不是代码编写服务。我现在正在准备一些教学,如果我完成后有时间我会看看。但是这个想法是在你上面写的小代码上使用 MPI 作为外部循环,使用 OpenMP 作为内部循环。应该很容易,你为什么不试一试呢?

标签: fortran mpi openmp openmpi


【解决方案1】:

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

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

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

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

请告诉我

希望对你有所帮助。

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

【讨论】:

  • 非常感谢您的帮助。我已经在我的帖子中修改了关于变量 ki 的示例代码。你的回答对我很有帮助。我可以再问一个问题吗?您使用 OpenMP 技术来并行化内部循环。仅并行化内部循环就足够了。但是,我正在编写的实际代码在这个示例中要复杂得多,我想并行化内循环和外循环。实际代码与此示例代码具有相同的结构。是否可以并行化内循环和外循环?再次非常感谢您的帮助。
  • @Kieran 根据 OpenMP 的概念,不可能并行化两个循环。最好的办法总是并行化外部循环,但对于您提到的情况,在我看来,并行化内部循环似乎足够有效。实际上,由于您对每个索引“i”的“es”进行求和,而“es”不是向量,因此并行化内部循环似乎更方便,以便在不使用 ATOMIC 或 CRITICAL 同步的情况下利用归约子句的好处选项。请注意,减少是最有效的。现在要并行化外部循环,您必须重新考虑变量的结构。
  • @Kieran 另外,如果您并行化外部循环,您可能会以不想要的顺序编写“et(i),es”。
  • 非常感谢您的帮助。我现在正在尝试使用 MPI 并行化外循环和 OpenMP 并行化内循环。您对“减少条款”的建议对我非常有用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多