有关openMP实施的建议

时间:2014-12-12 15:04:20

标签: multithreading fortran openmp reduction

我以前在相对简单的情况下使用openMP。由于我不是那种经验,我需要一些关于如何在我的下面的代码中最好地使用openMP的建议。

基本上我的代码通过使用Runge-Kutta四阶方案求解两个耦合微分方程,在数字上数字地积分两个向量(向上 un ) - 即每个及时前进四个临时步骤:

#define DUDT_P(STEP)     cmplx(0,1)*(   d(rng_0,1)*CONJG(un(rng_p1,STEP))*un(rng_p2,STEP) + \
                                        d(rng_0,2)*CONJG(un(rng_m1,STEP))*up(rng_p1,STEP) + \
                                        d(rng_0,3)*      un(rng_m2,STEP) *up(rng_m1,STEP) )

#define DUDT_N(STEP)     cmplx(0,1)*(   d(rng_0,1)*CONJG(up(rng_p1,STEP))*up(rng_p2,STEP) + \
                                        d(rng_0,2)*CONJG(up(rng_m1,STEP))*un(rng_p1,STEP) + \
                                        d(rng_0,3)*      up(rng_m2,STEP) *un(rng_m1,STEP) )

[...]

do ii = 2, nt+1 
        do jj = 1,nti

                ! ---------- STEP 1/4 ---------- THERE MUST BE A BARRIER/CONSISTENT MEMORY VIEW HERE
                dup(rng_0,1) = DUDT_P(1)
                up(:,2) = up(:,1) + dt2*dup(:,1)  

                dun(rng_0,1) = DUDT_N(1) 
                un(:,2) = un(:,1) + dt2*dun(:,1)

                ! ---------- STEP 2/4 ---------- THERE MUST BE A BARRIER/CONSISTENT MEMORY VIEW HERE
                dup(rng_0,2) = DUDT_P(2)
                up(:,3) = up(:,1) + dt2*dup(:,2)

                dun(rng_0,2) = DUDT_N(2)
                un(:,3) = un(:,1) + dt2*dun(:,2)

                ! ---------- STEP 3/4 ---------- THERE MUST BE A BARRIER/CONSISTENT MEMORY VIEW HERE
                dup(rng_0,3) = DUDT_P(3)
                up(:,4) = up(:,1) + dt*dup(:,3)

                dun(rng_0,3) = DUDT_N(3)
                un(:,4) = un(:,1) + dt*dun(:,3)

                ! ---------- STEP 4/4 ---------- THERE MUST BE A BARRIER/CONSISTENT MEMORY VIEW HERE
                dup(rng_0,4) = DUDT_P(4)
                up(:,1) = up(:,1) + dt6*dup(:,1) + dt3*dup(:,2) + dt3*dup(:,3) + dt6*dup(:,4) ! Full/actual time stepped solution (full RK4 solution)

                dun(rng_0,4) = DUDT_N(4)
                un(:,1) = un(:,1) + dt6*dun(:,1) + dt3*dun(:,2) + dt3*dun(:,3) + dt6*dun(:,4) ! Full/actual time stepped solution (full RK4 solution)

        end do

        saved(:,ii,1) = up(:,1) ! Save solution
        saved(:,ii,2) = un(:,1)
end do 

现在,由于每个向量大20个组件,每个临时时间步骤需要大量的算术运算。 您可以通过查看衍生的 DUDT_P() DUDT_N()(宏)函数来看到这一点,每个向量一个,并注意它们涉及元素方法算法向量之间的 rng _ * 条目(此处所有 rng _ * 列表指定了 up un 向量)。

有一个重要的约束,我在代码中也评论过。由于每个后续临时时间步的解决方案都取决于之前的步骤,因此在计算下一个temp之前,线程必须在 up un 向量上具有相同的更新视图。时间步骤解决方案(即共享)。

为了清楚变量,这是我用过的符号:

  • d(:,:)是常量(参数)交互系数。
  • up(1:20,1)和un(1:20,1)是目前已知的实际解决方案
  • up(1:20,2)和un(1:20,2)是第一个温度。时间步骤解决方案
  • up(1:20,3)和un(1:20,3)是第二个临时温度。时间步骤解决方案
  • up(1:20,4)和un(1:20,4)是第三个温度。时间步骤解决方案
  • 真实和临时的结构。解决方案不一定是我在这里选择的up(1:20,1:4)格式。
  • dup dun 变化率变量的内存视图必须在步骤4/4期间保持一致,用于计算最终/实时步骤解决方案。

我想过几种方法可以解决这个问题,然而,我陷入了困境:

  1. 四个温度中的每一个。步骤,一个线程可以通过为每个临时步骤制作两个部分来计算 DUDT_P()和另一个 DUDT_N()。但是,尝试此操作时会出现性能损失,因为我猜测, dup dun (例如 up un )需要共享vars,因为无法指定哪个线程执行哪两个部分?即缓存的 dup dun 变为无效,除非可以某种方式强制相同的线程仅在同一向量的每个临时步骤中进行计算。
  2. 不知何故在每个临时步骤中建立工作共享情况或使用减少条款,但是,我很难理解如何设置它。
  3. 感谢您的任何评论!

1 个答案:

答案 0 :(得分:1)

单核优化

  • 你有一个非常好的ADD / MUL平衡。在这种CPU绑定代码上,ifort或PGI可以提供比gfortran更好的性能。你使用这样的编译器吗? 您是否为CPU(-xAVX-xCORE-AVX2-xSSE4.2等)激活了最佳编译器选项?这可能会对您的特定示例产生很大影响。

  • 如果你明确地设置了循环边界:do k=1,20, 编译器将知道最适合循环的内容。

  • 你也可以玩内存对齐。有了英特尔编译器,你 可以把指令: !DIR$ ATTRIBUTES ALIGN : 32 :: dup, up, dun, un 声明变量后。 PGI编译器也可以。这将改善内存访问和编译器的矢量化可能性,因为您只能使用对齐的数据进行矢量化 在32字节边界上(或Xeon Phi上的64字节,以及旧CPU上的16字节)。

  • 当您的第一个维度为20时,数组的所有列也都是32字节 对齐。你可以使用指令!DIR$ VECTOR ALIGNED告诉编译器 在最内圈之前。

  • 请尝试-O2而不是-O3,因为-O3有时会变慢。

  • 尝试使用flush-to-zero编译器选项:使用非规范化数字会降低执行速度

OpenMP的

如果你想为up使用一个线程,为un使用一个线程,你可以尝试以下方法, 但是我真的不确定你会因为openMP障碍而获益(或事件不会失败......)。

线程0正在执行up,线程1正在执行un。放allocate很重要 openMP部分中的语句和初始化,以便尽可能地分配内存 线程(第一次触摸政策)。


在这里,您的代码已经修改了所有这些想法:

!DIR$ ATTRIBUTES ALIGN : 32 :: dup, up, dun, un

#define DUDT_P(STEP)     cmplx(0,1)*(   d(rng_0,1)*CONJG(un(rng_p1,STEP))*un(rng_p2,STEP) + \
                                        d(rng_0,2)*CONJG(un(rng_m1,STEP))*up(rng_p1,STEP) + \
                                        d(rng_0,3)*      un(rng_m2,STEP) *up(rng_m1,STEP) )

#define DUDT_N(STEP)     cmplx(0,1)*(   d(rng_0,1)*CONJG(up(rng_p1,STEP))*up(rng_p2,STEP) + \
                                        d(rng_0,2)*CONJG(up(rng_m1,STEP))*un(rng_p1,STEP) + \
                                        d(rng_0,3)*      up(rng_m2,STEP) *un(rng_m1,STEP) )

[...]

!$OMP PARALLEL SHARED( up,un,dup,dun, [...] ) PRIVATE( [...] ) num_threads(2)
ithread = omp_get_thread_num()

!DIR$ ATTRIBUTES ALIGN : 32 :: dup, up, dun, un
if (ithread == 0) then
  allocate (up(1:20,4), dup(1:20,4))
  ! Initialization is important to pin the memory to the cores
  up = 0.
  dun = 0.
else
  allocate (un(1:20,4), dun(1:20,4))
  ! Initialization is important to pin the memory to the cores
  un = 0.
  dun = 0.
endif

do ii = 2, nt+1
        do jj = 1,nti

            !$OMP BARRIER
                ! ---------- STEP 1/4 ---------- THERE MUST BE A BARRIER/CONSISTENT MEMORY VIEW HERE
            if (ithread == 0) then
                dup(rng_0,1) = DUDT_P(1)
                !DIR$ VECTOR ALIGNED
                do k=1,20
                  up(k,2) = up(k,1) + dt2*dup(k,1)
                end do
            else
                dun(rng_0,1) = DUDT_N(1)
                !DIR$ VECTOR ALIGNED
                do k=1,20
                  un(k,2) = un(k,1) + dt2*dun(k,1)
                end do
            endif

            !$OMP BARRIER
                ! ---------- STEP 2/4 ---------- THERE MUST BE A BARRIER/CONSISTENT MEMORY VIEW HERE
            if (ithread == 0) then
                dup(rng_0,2) = DUDT_P(2)
                !DIR$ VECTOR ALIGNED
                do k=1,20
                  up(k,3) = up(k,1) + dt2*dup(k,2)
                end do
            else
                dun(rng_0,2) = DUDT_N(2)
                !DIR$ VECTOR ALIGNED
                do k=1,20
                  un(k,3) = un(k,1) + dt2*dun(k,2)
                end do
            endif

            !$OMP BARRIER
                ! ---------- STEP 3/4 ---------- THERE MUST BE A BARRIER/CONSISTENT MEMORY VIEW HERE
            if (ithread == 0) then
                dup(rng_0,3) = DUDT_P(3)
                !DIR$ VECTOR ALIGNED
                do k=1,20
                  up(k,4) = up(k,1) + dt*dup(k,3)
                end do
            else
                dun(rng_0,3) = DUDT_N(3)
                !DIR$ VECTOR ALIGNED
                do k=1,20
                  un(k,4) = un(k,1) + dt*dun(k,3)
                end do
            endif

            !$OMP BARRIER
                ! ---------- STEP 4/4 ---------- THERE MUST BE A BARRIER/CONSISTENT MEMORY VIEW HERE
            if (ithread == 0) then
                dup(rng_0,4) = DUDT_P(4)
                !DIR$ VECTOR ALIGNED
                do k=1,20
                  up(k,1) = up(k,1) + dt6*dup(k,1) + dt3*dup(k,2) + dt3*dup(k,3) + dt6*dup(k,4) ! Full/actual time stepped solution (full RK4 solution)
                enddo
            else
                dun(rng_0,4) = DUDT_N(4)
                !DIR$ VECTOR ALIGNED
                do k=1,20
                  un(k,1) = un(k,1) + dt6*dun(k,1) + dt3*dun(k,2) + dt3*dun(k,3) + dt6*dun(k,4) ! Full/actual time stepped solution (full RK4 solution)
                enddo
            endif

        end do

        if (ithread == 0) then

          do k=1,20
            saved(k,ii,1) = up(k,1) ! Save solution
          end do

        else

          do k=1,20
            saved(k,ii,2) = un(k,1) ! Save solution
          end do

        end if

end do

if (ithread == 0) then
  deallocate(un,up,dun,dup)
end if

!$OMP END PARALLEL