我有一个相对简单的循环,我用蛮力方法计算粒子系统的净加速度。
我有一个工作的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
答案 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
......,现在回到问题]
这里有两个问题:
bodyArray(j)%acc
;多个线程将尝试更新相同的元素,并且没有这些更新的协调。垃圾结果。使用critical
部分或命令语句序列化代码;如果你做对了,你也会像开始使用OpenMP之前那样慢。bodyArray
元素的模式对缓存不友好。我不会惊讶地发现,即使您在没有序列化计算的情况下处理数据竞争,缓存不友好的影响也是产生比原始代码慢的代码。现代CPU的计算速度非常快,但内存系统很难为野兽提供动力,因此缓存效果可能非常大。试图同时在同一个rank-1阵列上运行两个循环,这实际上就是你的代码所做的,绝不会(?)以最大速度将数据转移到缓存中。我个人会尝试以下方法。我不能保证这会更快,但是(我认为)比修复当前的方法更容易,并且像手套一样适合OpenMP。 我确实有一种唠叨的怀疑,认为这太复杂了,但我还没有更好的想法。
首先,创建一个实数的二维数组,称之为forces
,其中元素force(i,j)
是元素i
对j
施加的力。然后,这样的一些代码(未经测试,如果你想关注这一行,这是你的责任)
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)
索引转换为该阵列的一维索引。这样可以节省存储空间,并且可以更容易实现缓存友好。