函数调用对性能有多少影响?


13

对于代码模块化,可读性和互操作性,尤其是在OOP中,必须将功能提取到方法或函数中。

但这意味着将进行更多的函数调用。

将我们的代码拆分为方法或函数实际上如何影响现代*语言的性能?

*最受欢迎的语言:C,Java,C ++,C#,Python,JavaScript,Ruby ...



1
我认为,值得一提的每一种语言实现都已经内联了数十年。IOW,开销恰好是0。
JörgW Mittag

1
“将进行更多的函数调用”通常是不正确的,因为许多调用将通过处理代码和内联东西的各种编译器/解释器来优化其开销。如果您的语言没有这些优化,我可能不会认为它是现代的。
Ixrec

2
它将如何影响性能?取决于您使用的是哪种特定语言,实际代码的结构是什么,可能使用的是哪个版本的编译器,甚至您使用的平台,它会使它变快,变慢或不更改它重新运行。您得到的每个答案都将是这种不确定性的变体,更多的单词和更多的支持证据。
GrandOpener

1
影响(如果有的话)是如此之小,以至于您(一个人)将永远不会注意到它。还有其他更重要的事情要担心。就像制表符是5还是7。
MetaFight

Answers:


21

也许。编译器可能会决定“嘿,这个函数只被调用了几次,我应该针对速度进行优化,因此我将内联这个函数”。本质上,编译器会将函数调用替换为函数主体。例如,源代码如下所示。

void DoSomething()
{
   a = a + 1;
   DoSomethingElse(a);
}

void DoSomethingElse(int a)
{
   b = a + 3;
}

编译器决定内联DoSomethingElse,代码变为

void DoSomething()
{
   a = a + 1;
   b = a + 3;
}

当不内联函数时,是的,会影响性能以进行函数调用。但是,它是如此的微小,以至于只有极高性能的代码才会担心函数调用。在这类项目中,代码通常以汇编形式编写。

函数调用(取决于平台)通常涉及数十条指令,其中包括保存/恢复堆栈。一些函数调用包含跳转和返回指令。

但是还有其他一些因素可能会影响函数调用的性能。被调用的函数可能未加载到处理器的高速缓存中,从而导致高速缓存未命中,并迫使内存控制器从主RAM中获取该函数。这可能会对性能造成重大影响。

简而言之:函数调用可能会或可能不会影响性能。唯一的方法就是分析您的代码。不要试图猜测慢速代码的位置,因为编译器和硬件有一些难以置信的窍门。分析代码以获取慢点的位置。


1
在现代编译器(gcc,clang)中,我真的很在意它们为大型函数中的循环创建了非常糟糕的代码的情况。由于内联,无法将循环提取到静态函数中。在某些情况下,将循环提取到外部函数中可以显着提高速度(可在基准中测量)。
gnasher729

1
我会背负这一点,并说OP应该对过早优化
Patrick

1
@帕特里克·宾果 如果要进行优化,请使用探查器来查看慢速节的位置。不要猜 通常,您可以感觉到慢速段可能在哪里,但可以通过探查器进行确认。
CHendrix '16

@ gnasher729要解决这一特定问题,不仅需要分析器,而且还需要学习阅读反汇编后的机器代码。虽然有过早的优化,但没有过早的学习(至少在软件开发中)。
rwong

可能会如果你调用一个函数一万次也有这个问题,但你更可能有这些有显著更大的影响等问题。
萧伯纳

5

这是编译器或运行时(及其选项)的实现问题,不能肯定地说。

在C和C ++中,某些编译器将基于优化设置进行内联调用-在查看诸如https://gcc.godbolt.org/之类的工具时,通过检查生成的程序集可以很容易地看出这一点。

其他语言(例如Java)将此作为运行时的一部分。这是JIT的一部分,并在此SO问题中进行了详细说明。特别查看HotSpotJVM选项

-XX:InlineSmallCode=n 仅当其生成的本机代码大小小于此大小时才内联先前编译的方法。缺省值随运行JVM的平台而异。
-XX:MaxInlineSize=35 内联方法的最大字节码大小。
-XX:FreqInlineSize=n 要内联的频繁执行方法的最大字节码大小。缺省值随运行JVM的平台而异。

因此,是的,HotSpot JIT编译器将内联满足某些条件的方法。

影响这一点,是很难确定,因为每个JVM(或编译器)可以做不同的事情,并试图用语言的广泛行程回答几乎是肯定不对的。只有在适当的运行环境中对代码进行性能分析并检查编译后的输出,才能正确确定影响。

可以将其视为错误的方法,因为CPython没有内联,但是Jython(在JVM中运行的Python)内联了一些调用。同样,MRI Ruby不会内联,而JRuby会内联,而ruby2c是将Ruby转换为C的编译器……然后可以内联或不内联,具体取决于编译时使用的C编译器选项。

语言不是内联的。实现可能


5

您在错误的位置寻找性能。函数调用的问题不是它们花费很多。还有另一个问题。函数调用可能是完全免费的,您仍然会遇到其他问题。

功能就像信用卡一样。由于您可以轻松使用它,因此您倾向于使用它的次数可能会更多。假设您称它比需要多20%。然后,典型的大型软件包含多个层,每个调用函数都在下面的层中,因此1.2的系数可以与层数复合。(例如,如果有五层,并且每层的减速系数为1.2,则复合的减速系数为1.2 ^ 5或2.5。)这只是考虑的一种方法。

这并不意味着您应该避免函数调用。意思是,当代码启动并运行时,您应该知道如何查找并消除浪费。关于如何在stackexchange网站上执行此操作,有很多非常好的建议。 是我的贡献之一。

添加:小例子。有一次,我在一个工厂车间的软件团队工作,该软件可以跟踪一系列工作指令或“工作”。有一个功能JobDone(idJob),如果一个作业已经完成,可以告诉。当所有子任务都完成时,一个工作就完成了,而当所有子操作都完成时,每个工作都完成了。所有这些事情都在关系数据库中进行了跟踪。一次调用另一个函数可以提取所有这些信息,即JobDone所谓的另一个函数,可以查看工作是否完成,然后将其余信息扔掉。然后人们可以轻松地编写如下代码:

while(!JobDone(idJob)){
    ...
}

要么

foreach(idJob in jobs){
    if (JobDone(idJob)){
        ...
    }
}

明白了吗?该函数是如此“强大”且易于调用,以至于调用过多。因此,性能问题不是指令进出该功能。那是需要一种更直接的方法来判断工作是否完成。同样,该代码可能已嵌入成千上万行本来无害的代码中。每个人都试图做的事是提前修复它,但这就像试图在黑暗的房间里扔飞镖一样。相反,您需要运行它,然后让“慢速代码”告诉您它是什么,只需花费时间即可。为此,我使用随机暂停


1

我认为这确实取决于语言和功能。尽管c和c ++编译器可以内联许多函数,但Python或Java并非如此。

虽然我不知道Java的具体细节(除了每个方法都是虚拟的,但我建议您更好地检查文档),但是在Python中,我确信没有内联,没有尾递归优化和函数调用都非常昂贵。

Python函数基本上是可执行对象(实际上,您也可以定义call()方法使对象实例成为函数)。这意味着调用它们有很多开销。

当您在函数内部定义变量时,解释器将使用LOADFAST而不是字节码中的常规LOAD指令,从而使您的代码更快。

另一件事是,当您定义可调用对象时,便可以使用记忆模式等模式,它们可以有效地大大加快计算速度(以使用更多内存为代价)。基本上,这始终是一个权衡。函数调用成本还取决于参数,因为它们决定了您实际上必须在堆栈上复制多少东西(因此,在c / c ++中,通常的做法是通过指针/引用而不是通过值来传递诸如结构之类的大参数)。

我认为您的问题实际上过于广泛,无法在stackexchange上完全回答。

我建议您从一种语言开始学习高级文档,以了解该特定语言如何实现函数调用。

您将对在此过程中学到的东西感到惊讶。

如果您有特定的问题,请进行测量/分析并确定天气,最好创建一个函数或复制/粘贴等效代码。

我认为,如果您提出更具体的问题,则更容易获得更具体的答案。


引用您的话:“我认为您的问题实际上太广泛了,无法在stackexchange上完全回答。” 那我如何缩小范围呢?我很乐意看到一些代表函数调用对性能影响的实际数据。我不在乎什么语言,我只是好奇地看到一个更详细的解释,如我所说,在可能的情况下提供数据备份。
dabadaba '16

关键是它取决于语言。在C和C ++中,如果函数是内联的,则影响为0。如果未内联,则取决于其参数,是否在缓存中,等等...
ingframin

1

不久前,我测量了Xenon PowerPC上直接和虚拟C ++函数调用的开销

所讨论的函数具有单个参数和单个返回,因此参数传递发生在寄存器上。

长话短说,与内联函数调用相比,直接(非虚拟)函数调用的开销约为5.5纳秒,即18个时钟周期。与内联相比,虚拟函数调用的开销为13.2纳秒,即42个时钟周期。

这些时序在不同的处理器系列上可能有所不同。我的测试代码在这里;您可以在硬件上运行相同的实验。在CFastTimer实现中使用rdtsc这样的高精度计时器。系统时间()不够精确。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.