使用OpenMP并行化嵌套循环

时间:2014-08-07 06:45:14

标签: fortran openmp

我有一个相对简单的循环,我用蛮力方法计算粒子系统的净加速度。

我有一个工作的OpenMP循环,循环遍历每个粒子并将其与其他所有粒子进行比较,以获得n ^ 2的复杂性:

   !$omp parallel do private(i) shared(bodyArray, n) default(none)
   do i = 1, n
        !acc is real(real64), dimension(3)
        bodyArray(i)%acc = bodyArray(i)%calcNetAcc(i, bodyArray)
   end do

效果很好。

我现在要做的是通过使用来自F(a-> b)= -F(b->的力的事实来计算每个身体上的力来减少我的计算时间; a),减少计算一半的交互次数(n ^ 2/2)。我在这个循环中做了什么:

call clearAcceleration(bodyArray) !zero out acceleration

    !$omp parallel do private(i, j) shared(bodyArray, n) default(none)
       do i = 1, n
          do j = i, n

             if ( i /= j .and. j > i) then 
                bodyArray(i)%acc = bodyArray(i)%acc + bodyArray(i)%accTo(bodyArray(j))
                bodyArray(j)%acc = bodyArray(j)%acc - bodyArray(i)%acc
             end if

          end do
       end do

但是我在这个循环并行化方面遇到了很多困难,我不断得到垃圾结果。我认为这与这一行有关:

bodyArray(j)%acc = bodyArray(j)%acc - bodyArray(i)%acc

并且力量没有与所有不同的' j'写给它。 我尝试过使用原子语句,但是数组变量不允许这样做。所以我尝试了批评,但这会增加大约20的时间,但仍然没有给出正确的结果。我也尝试添加一个有序的语句,但是我只得到NaN获得我的所有结果。 是否有一个简单的解决方法可以使这个循环使用OpenMP?

工作代码,速度略有提升但不是我想要的~2倍。

!$omp parallel do private(i, j) shared(bodyArray, forces, n) default(none) schedule(guided)
      do i = 1, n
         do j = 1, i-1
               forces(j, i)%vec = bodyArray(i)%accTo(bodyArray(j))
               forces(i, j)%vec = -forces(j, i)%vec
         end do
      end do

      !$omp parallel do private(i, j) shared(bodyArray, n, forces) schedule(static)
      do i = 1, n
         do j = 1, n
            bodyArray(i)%acc = bodyArray(i)%acc + forces(j, i)%vec
         end do
      end do

1 个答案:

答案 0 :(得分:5)

使用您当前的方法和数据结构,您将很难通过OpenMP获得良好的加速。考虑循环嵌套

!$omp parallel do private(i, j) shared(bodyArray, n) default(none)
   do i = 1, n
      do j = i, n

         if ( i /= j .and. j > i) then 
            bodyArray(i)%acc = bodyArray(i)%acc + bodyArray(i)%accTo(bodyArray(j))
            bodyArray(j)%acc = bodyArray(j)%acc - bodyArray(i)%acc
         end if

      end do
   end do

[实际上,在你考虑它之前,按照以下方式修改它......

!$omp parallel do private(i, j) shared(bodyArray, n) default(none)
   do i = 1, n
      do j = i+1, n

            bodyArray(i)%acc = bodyArray(i)%acc + bodyArray(i)%accTo(bodyArray(j))
            bodyArray(j)%acc = bodyArray(j)%acc - bodyArray(i)%acc

      end do
   end do

......,现在回到问题]

这里有两个问题:

  1. 由于您已经tw,,您已经更新了数据竞赛bodyArray(j)%acc;多个线程将尝试更新相同的元素,并且没有这些更新的协调。垃圾结果。使用critical部分或命令语句序列化代码;如果你做对了,你也会像开始使用OpenMP之前那样慢。
  2. 访问bodyArray元素的模式对缓存不友好。我不会惊讶地发现,即使您在没有序列化计算的情况下处理数据竞争,缓存不友好的影响也是产生比原始代码慢的代码。现代CPU的计算速度非常快,但内存系统很难为野兽提供动力,因此缓存效果可能非常大。试图同时在同一个rank-1阵列上运行两个循环,这实际上就是你的代码所做的,绝不会(?)以最大速度将数据转移到缓存中。
  3. 我个人会尝试以下方法。我不能保证这会更快,但是(我认为)比修复当前的方法更容易,并且像手套一样适合OpenMP。 我确实有一种唠叨的怀疑,认为这太复杂了,但我还没有更好的想法。

    首先,创建一个实数的二维数组,称之为forces,其中元素force(i,j)是元素ij施加的力。然后,这样的一些代码(未经测试,如果你想关注这一行,这是你的责任)

    forces = 0.0  ! Parallelise this if you want to
    !$omp parallel do private(i, j) shared(forces, n) default(none)
       do i = 1, n
          do j = 1, i-1
                forces(i,j) = bodyArray(i)%accTo(bodyArray(j)) ! if I understand correctly
          end do
       end do
    

    然后对每个粒子上的力进行求和(并得到以下权利,我没有仔细检查过)

    !$omp parallel do private(i) shared(bodyArray,forces, n) default(none)
    do i = 1, n
          bodyArray(i)%acc = sum(forces(:,i))
    end do
    

    正如我上面所写的那样,计算速度非常快,如果你有足够的记忆力,那么它往往值得花一些时间进行交易。

    现在你所拥有的可能是forces上的循环嵌套中的负载平衡问题。默认情况下,大多数OpenMP实现都会执行静态的工作分配(这不是标准所要求的,但似乎最常见,请查看您的文档)。因此,线程1将获得要处理的第一个n/num_threads行,但这些是您正在计算的三角形顶部的非常小的行。线程2将获得更多工作,线程3将更多,等等。您可能只需在schedule(dynamic)指令中添加parallel子句即可,您可能需要更加努力地平衡负载。

    您可能还想查看我的代码段 wrt 缓存友好性并根据需要进行调整。而你可能会发现,如果按照我的建议,你最好使用原始代码,将计算量减半并不能节省太多时间。

    另一种方法是将forces的下三角(或上三角)打包成一个排名1阵列,并使用一些花哨的索引算法将2D (i,j)索引转换为该阵列的一维索引。这样可以节省存储空间,并且可以更容易实现缓存友好。