过度使用函数调用会影响性能吗?特别是在Fortran中。

5
我习惯于编写有很多函数的代码,我发现这样可以使它更清晰。但现在我正在用Fortran编写一些需要非常高效的代码,我想知道过度使用函数是否会减慢它的速度,或者编译器是否会推出正在发生什么并优化它?
我知道在Java/Python等语言中,每个函数都是一个对象,因此创建许多函数需要在内存中创建它们。我也知道在Haskell中,函数被彼此缩减,所以那里几乎没有区别。
有谁知道Fortran的情况吗?使用意图/纯函数/声明较少的本地变量/其他任何内容是否有差异?

每个函数在Java中以何种方式成为一个对象?调用函数并不会创建一个对象... 在Python中可能会这样做,但我怀疑。 (您可以通过反射获取Java中方法的表示形式,但那不是同一件事。) - Jon Skeet
抱歉,之前表达没说清楚。我指的是Java(和Python)中匿名函数需要创建并存储在内存中的方式,这需要花费时间。如果每次在我的函数中创建/分配内存时需要为所有本地变量进行此操作,则减少函数的数量可能是值得的。但是,如果它们在开始时就分配内存,则可能不会有太大区别。 以前称它们为内存中的“对象”是一个错误的选择(尽管我认为它们在Python中实际上确实是对象)。 - Samizdis
如果局部变量在堆栈上,它们将在一个指令中创建并在一个指令中删除。 - KLee1
我们应该忘记小的效率问题,大约97%的时间:过早优化是万恶之源。——唐纳德·克努斯 - xslittlegrass
4个回答

9

对于基于堆栈的语言(如Fortran),函数调用会带来性能成本。它们必须添加到堆栈中等等。

因此,大多数编译器将尝试积极地内联函数调用,如果可能的话。大多数情况下,编译器会在是否内联某些程序中做出正确的选择。

这个自动内联过程意味着编写函数不会产生额外的成本(完全没有成本)。

这意味着您应该尽可能清晰、有组织地编写代码,很可能编译器会为您进行这些优化。更重要的是,您解决问题的整体策略比担心函数调用的性能更有效。


2

尽可能以最简单和最好结构化的方式编写代码,然后在编写和测试完成后,您可以对其进行剖析以查看是否存在需要优化的热点。只有在这一点上,您才应该关注微观优化,如果您的编译器正在执行其工作,则可能不需要进行微观优化。


1

我刚刚花了整个上午调试一个由混合C和Fortran组成的应用程序,当然它使用了很多函数。我发现(通常也是如此)的不是函数慢,而是某些函数调用(非常少)根本就不需要执行。例如,清除内存块,只是为了整洁,但高频率地这样做。

这不是语言的功能,也不是内联的功能。函数调用可能是免费的,但您仍然会遇到调用树比必要更繁茂的问题。您需要找出在哪里进行修剪。这是我依赖的“分析”方法。

无论您做什么,都要找出需要修复的问题。不要猜测。许多人不认为这种问题是猜测,但当他们发现自己问“这会起作用吗?那会有帮助吗?”时,他们是在摸索,而不是找出问题所在。一旦他们知道问题所在,解决方案就显而易见了。


0

通常在Fortran中,子程序/函数调用的开销非常小。虽然语言标准没有指定参数传递机制,但典型的实现方式是“按引用传递”,因此不涉及复制,只需设置新的过程。在大多数现代架构上,这几乎没有开销。选择好的算法通常比微观优化更重要。

一个例外是当编译器必须创建临时数组时,调用可能会很快。例如,如果实际参数是非连续数组子段,并且被调用的过程参数是普通的连续数组,则会出现这种情况。假设虚拟参数是dimension(:)。使用dimension(:)的数组进行调用很简单。如果在调用中请求非单位步幅,例如array(1:12:3),则数组是非连续的,编译器可能需要创建一个临时副本。假设实际参数是dimension(:,:)。如果调用具有array(:,j),则子数组是连续的,因为在Fortran中,第一个索引在内存中变化最快,不应需要复制。但是,array(i,:)是非连续的,可能需要一个临时副本。

一些编译器具有选项,可以在需要临时数组副本时警告您,以便您可以更改代码(如果您愿意)。


当然,你是对的,但人们忽略了一个点。这些东西是相对的,不仅仅是快或慢。当进入和退出例程时,它有很多事情要做。如果它花费超过总时间的约5%来做这件事,例如,如果它正在调用通常可以执行某些复杂操作但通常几乎不执行操作的例程,则样本应该找到它,并且应进行一些更改。例如:LAPACK中的LSAME通常只比较两个字符,但那家伙的注意力太集中了。 - Mike Dunlavey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接