【问题标题】:Performance loss using allocatable arrays使用可分配数组的性能损失
【发布时间】:2016-03-09 20:19:11
【问题描述】:

我有一个 Fortran90 程序 (Packmol),在此之前,它是通过静态内存分配实现的。

我更改了代码以使用动态分配,这样所有数组都在开始时分配。在某些示例中,我的性能损失为 400%。

然后,我验证了即使数组的大小与我使用静态分配时的大小相同,问题仍然存在。也就是说,如果我将分配从 double precision :: x(1000) 类似于 double precision, allocatable :: x(:) allocate(x(1000)) 这足以导致性能损失。当然,当所有需要动态分配的数组都这样做时,大约是 30。

有没有办法以更有效的方式分配数组以减少性能损失?还是有人有不同的建议?

非常感谢。

编辑:不知何故,问题解决了。动态版本现在只比预期的静态版本慢一点。我真的不知道是什么导致了之前的大幅放缓。

【问题讨论】:

  • 好吧,我没有放代码,因为它是一个大程序。代码可在此处获得:github.com/leandromartinez98/packmol。在子例程 setsizes.f90 中有一个分支,其中所有数组都是动态分配的。
  • 该例程是否经常调用?调用之间分配的大小会改变吗? (或者它们是为整个程序运行而设置的。)allocate-ing 本身的成本并没有那么高。这取决于它的使用方式。
  • 如您所见,很可能有人会通过这样的链接。即使是现在的答案也只是猜测。您应该准备一个重现问题的最小示例找到差异较大的子程序并进行操作。
  • 分析您的代码 - 在更改为可分配数组之前和之后。确保您正在测试的构建启用了适当的优化级别,并且没有启用运行时调试选项,例如数组边界检查。
  • 请注意,代码不符合要求。在解决这个问题之前,思考这个问题没有什么意义 - 变化可能仅仅是由于内存损坏或类似情况而选择了不同的执行路径。

标签: arrays fortran


【解决方案1】:

造成这种性能损失的原因有很多:

1) 静态数组总是在 BSS 上分配(请参阅Where are static variables stored (in C/C++)?),而“已分配”数组可以在堆或堆栈上分配。堆栈上的分配比堆上快得多。一个好的编译器可以生成尽可能多地在堆栈上分配的代码。

2) 您可能在循环中有分配/解除分配语句。每次内存分配都需要一些时间。一个好的编译器可以避免在每次分配时物理分配一些内存,而是重新使用已释放的空间。

3) 编译器在编译时就知道静态数组的维度,所以它会做一些额外的优化。

4) 如果您有多维数组,则无法在编译时计算元素的地址。例如A(5,6,7)的地址是5 + 6*n1 + 7*n1*n2,其中n1和n2是A的维度:A(n1,n2,n3)。对于静态数组,编译器可以优化这部分。此外,如果维度 n1,n2,... 是 2 的幂,编译器不会进行整数乘法运算,而是生成速度快 3 倍的位移。

数字 3) 是最有可能的。您可以为在编译时知道合理上限的数组保留一些静态数组,这些数组相对较小(大致

根据经验,只能静态分配小数组:大多数 1D 数组、一些小的 2D 数组和微小的 3D 数组。将所有其余部分转换为动态分配,因为它们可能无法放入堆栈。

如果你有一些频繁的分配/取消分配,因为你在循环中调用了一个子例程,例如:

 do i=1,10000000
    call work(a,b)
 end do

 subroutine work(a,b)
  ...
  allocate (c)
  ...
  deallocate (c)
 end

如果c 始终具有相同的维度,您可以将其作为子例程的参数,或者作为在调用 work 之前仅分配一个的全局变量:

 use module_where_c_is_defined

 allocate (c)
 do i=1,10000000
    call work(a,b)
 end do
 deallocate(c)

 subroutine work(a,b)
  use module_where_c_is_defined
  if (.not.allocated(c)) then
    stop 'c is not allocated'
  endif
  ...
 end

【讨论】:

  • 你会混淆静态数组和自动数组吗?静态数组在静态存储中。它们需要比堆栈(主函数堆栈除外)更长的生命周期。它们通常直接存在于可执行二进制文件中,尤其是在它们被初始化时。
  • 非常感谢您的详细解答。实际上,我在程序中的任何时候都不会释放任何向量。所有向量都在主程序的开头分配,并且不再更改,它们通过模块共享或作为参数传递给以固定尺寸声明它们的子程序。当然,编译器优化是一个问题。我不认为这在我的代码中会那么重要。使用 ifort 时,我在使用动态内存分配时会减速 100%。我将尝试仅动态分配最关键的数组。
  • 静态数组,鉴于该术语的通常含义,通常不会放入堆栈 - 它们通常放入数据或 BSS 段。也许您正在考虑自动数组,尽管这些可能存储在堆栈或堆中,具体取决于实现。我不知道将数组数据(相对于数组的描述符)存储在堆栈上的可分配数组的任何实现 - 它们都使用堆(我只能想到非常有限的情况,其中基于堆栈的可分配存储存储甚至是可能)。
  • 我很好奇一些非常具体的事情:分配数组的顺序有什么不同吗?在单个语句中分配多个数组可能与在多个语句中分配它们不同?有没有办法在分配向量时优化它们在内存中的位置?在执行的同时所有维度都已知的情况下,是否可以模拟静态内存分配?
  • @leandro 我认为一次性分配(远?)更多有效。最好还是尽早做。我看不出有任何方法可以让您获得无法修复的性能降级。这必须是可修复的。我想如果一切都做对了,你可能会看到最小的效果(如果有的话)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-13
  • 2016-12-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多