在 Fortran90 中操作和分配多维数组中的子数组时,我偶然发现了一个有趣的性能怪癖。
Fortran90 引入了操作数组子部分的能力,我看到一些地方建议使用这种“切片”方法而不是循环来执行数组操作。例如,如果我必须添加两个数组,a
和 b
大小为 10 时,最好这样写:c(1:10) = a(1:10) + b(1:10)
或者c = a + b
代替
do i = 1, 10
c(i) = a(i) + b(i)
end do
我对简单的一维和二维数组尝试了这种方法,发现使用“切片”表示法速度更快。然而,在多维数组中分配这样的结果时,事情开始变得有点有趣。
首先,我必须为我相当粗糙的绩效衡量练习道歉。我什至不确定我采用的方法是否是计时和测试代码的正确方法,但我对测试的定性结果相当有信心。
program main
implicit none
integer, parameter :: mSize = 10000
integer :: i, j
integer :: pCnt, nCnt, cntRt, cntMx
integer, dimension(mSize, mSize) :: a, b
integer, dimension(mSize, mSize, 3) :: c
pCnt = 0
call SYSTEM_CLOCK(nCnt, cntRt, cntMx)
print *, "First call: ", nCnt-pCnt
pCnt = nCnt
do j = 1, mSize
do i = 1, mSize
a(i, j) = i*j
b(i, j) = i+j
end do
end do
call SYSTEM_CLOCK(nCnt, cntRt, cntMx)
print *, "Created Matrices: ", nCnt-pCnt
pCnt = nCnt
! OPERATIONS BY SLICING NOTATION
!c(1:mSize, 1:mSize, 1) = a + b
!c(1:mSize, 1:mSize, 2) = a - b
!c(1:mSize, 1:mSize, 3) = a * b
! OPERATIONS WITH LOOP
do j = 1, mSize
do i = 1, mSize
c(i, j, 1) = a(i, j) + b(i, j)
c(i, j, 2) = a(i, j) - b(i, j)
c(i, j, 3) = a(i, j) * b(i, j)
end do
end do
call SYSTEM_CLOCK(nCnt, cntRt, cntMx)
print *, "Added Matrices: ", nCnt-pCnt
pCnt = nCnt
end program main
可以看出,我有两种操作方法并将两个大型 2D 数组分配到 3D 数组中。我非常赞成使用切片符号,因为它帮助我编写更短、更优雅的代码。但是在观察到我的代码有多严重缓慢时,我被迫重新检查切片符号的能力而不是循环内的计算。
我使用 GNU Fortran 4.8.4 for Ubuntu 14.04 使用和不使用 -O3 标志运行了上面的代码
一种。切片符号
5 Runs - 843, 842, 842, 841, 859
Average - 845.4
湾循环计算
5 Runs - 1713, 1713, 1723, 1711, 1713
Average - 1714.6
一种。切片符号
5 Runs - 545, 545, 544, 544, 548
Average - 545.2
湾循环计算
5 Runs - 479, 477, 475, 472, 472
Average - 475
我发现非常有趣的是,没有 -O3 标志,切片符号继续比循环执行得更好。但是,使用 -O3 标志会导致这种优势完全消失。相反,在这种情况下使用数组切片符号变得有害。
事实上,对于我相当大的 3D 并行计算代码,结果证明这是一个重要的瓶颈。我强烈怀疑在将低维数组分配给高维数组期间形成临时数组是这里的罪魁祸首。但是为什么在这种情况下优化标志没有优化分配呢?
此外,我觉得指责 -O3 标志不是一件值得尊敬的事情。那么临时数组真的是罪魁祸首吗?还有什么我可能会遗漏的吗?任何见解都对加快我的代码非常有帮助。谢谢!
最佳答案
在进行任何性能比较时,您必须将苹果与苹果和橙子与橙子进行比较。我的意思是你并不是真正比较同一件事。即使它们产生相同的结果,它们也完全不同。
这里发挥作用的是内存管理,想想操作过程中的缓存故障。如果您按照 haraldkl 的建议将循环版本转换为 3 个不同的循环,您肯定会获得类似的性能。
发生的情况是,当您在同一个循环中组合 3 个赋值时,右侧有很多缓存重用,因为所有 3 个赋值在右侧共享相同的变量。 a
的每个元素或 b
对于循环版本,只有一次加载到缓存和寄存器中,而对于数组操作版本,a
的每个元素或 b
被加载 3 次。这就是区别所在。数组的大小越大,差异就越大,因为您将获得更多的缓存错误和更多的元素重新加载到寄存器中。
关于performance - Fortran多维子数组性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32384533/