对于代码模块化,可读性和互操作性,尤其是在OOP中,必须将功能提取到方法或函数中。
但这意味着将进行更多的函数调用。
将我们的代码拆分为方法或函数实际上如何影响现代*语言的性能?
*最受欢迎的语言:C,Java,C ++,C#,Python,JavaScript,Ruby ...
对于代码模块化,可读性和互操作性,尤其是在OOP中,必须将功能提取到方法或函数中。
但这意味着将进行更多的函数调用。
将我们的代码拆分为方法或函数实际上如何影响现代*语言的性能?
*最受欢迎的语言:C,Java,C ++,C#,Python,JavaScript,Ruby ...
Answers:
也许。编译器可能会决定“嘿,这个函数只被调用了几次,我应该针对速度进行优化,因此我将内联这个函数”。本质上,编译器会将函数调用替换为函数主体。例如,源代码如下所示。
void DoSomething()
{
a = a + 1;
DoSomethingElse(a);
}
void DoSomethingElse(int a)
{
b = a + 3;
}
编译器决定内联DoSomethingElse
,代码变为
void DoSomething()
{
a = a + 1;
b = a + 3;
}
当不内联函数时,是的,会影响性能以进行函数调用。但是,它是如此的微小,以至于只有极高性能的代码才会担心函数调用。在这类项目中,代码通常以汇编形式编写。
函数调用(取决于平台)通常涉及数十条指令,其中包括保存/恢复堆栈。一些函数调用包含跳转和返回指令。
但是还有其他一些因素可能会影响函数调用的性能。被调用的函数可能未加载到处理器的高速缓存中,从而导致高速缓存未命中,并迫使内存控制器从主RAM中获取该函数。这可能会对性能造成重大影响。
简而言之:函数调用可能会或可能不会影响性能。唯一的方法就是分析您的代码。不要试图猜测慢速代码的位置,因为编译器和硬件有一些难以置信的窍门。分析代码以获取慢点的位置。
这是编译器或运行时(及其选项)的实现问题,不能肯定地说。
在C和C ++中,某些编译器将基于优化设置进行内联调用-在查看诸如https://gcc.godbolt.org/之类的工具时,通过检查生成的程序集可以很容易地看出这一点。
其他语言(例如Java)将此作为运行时的一部分。这是JIT的一部分,并在此SO问题中进行了详细说明。特别查看HotSpot的JVM选项
-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编译器选项。
语言不是内联的。实现可能。
您在错误的位置寻找性能。函数调用的问题不是它们花费很多。还有另一个问题。函数调用可能是完全免费的,您仍然会遇到其他问题。
功能就像信用卡一样。由于您可以轻松使用它,因此您倾向于使用它的次数可能会更多。假设您称它比需要多20%。然后,典型的大型软件包含多个层,每个调用函数都在下面的层中,因此1.2的系数可以与层数复合。(例如,如果有五层,并且每层的减速系数为1.2,则复合的减速系数为1.2 ^ 5或2.5。)这只是考虑的一种方法。
这并不意味着您应该避免函数调用。意思是,当代码启动并运行时,您应该知道如何查找并消除浪费。关于如何在stackexchange网站上执行此操作,有很多非常好的建议。 这是我的贡献之一。
添加:小例子。有一次,我在一个工厂车间的软件团队工作,该软件可以跟踪一系列工作指令或“工作”。有一个功能JobDone(idJob)
,如果一个作业已经完成,可以告诉。当所有子任务都完成时,一个工作就完成了,而当所有子操作都完成时,每个工作都完成了。所有这些事情都在关系数据库中进行了跟踪。一次调用另一个函数可以提取所有这些信息,即JobDone
所谓的另一个函数,可以查看工作是否完成,然后将其余信息扔掉。然后人们可以轻松地编写如下代码:
while(!JobDone(idJob)){
...
}
要么
foreach(idJob in jobs){
if (JobDone(idJob)){
...
}
}
明白了吗?该函数是如此“强大”且易于调用,以至于调用过多。因此,性能问题不是指令进出该功能。那是需要一种更直接的方法来判断工作是否完成。同样,该代码可能已嵌入成千上万行本来无害的代码中。每个人都试图做的事是提前修复它,但这就像试图在黑暗的房间里扔飞镖一样。相反,您需要运行它,然后让“慢速代码”告诉您它是什么,只需花费时间即可。为此,我使用随机暂停。
我认为这确实取决于语言和功能。尽管c和c ++编译器可以内联许多函数,但Python或Java并非如此。
虽然我不知道Java的具体细节(除了每个方法都是虚拟的,但我建议您更好地检查文档),但是在Python中,我确信没有内联,没有尾递归优化和函数调用都非常昂贵。
Python函数基本上是可执行对象(实际上,您也可以定义call()方法使对象实例成为函数)。这意味着调用它们有很多开销。
但
当您在函数内部定义变量时,解释器将使用LOADFAST而不是字节码中的常规LOAD指令,从而使您的代码更快。
另一件事是,当您定义可调用对象时,便可以使用记忆模式等模式,它们可以有效地大大加快计算速度(以使用更多内存为代价)。基本上,这始终是一个权衡。函数调用成本还取决于参数,因为它们决定了您实际上必须在堆栈上复制多少东西(因此,在c / c ++中,通常的做法是通过指针/引用而不是通过值来传递诸如结构之类的大参数)。
我认为您的问题实际上过于广泛,无法在stackexchange上完全回答。
我建议您从一种语言开始学习高级文档,以了解该特定语言如何实现函数调用。
您将对在此过程中学到的东西感到惊讶。
如果您有特定的问题,请进行测量/分析并确定天气,最好创建一个函数或复制/粘贴等效代码。
我认为,如果您提出更具体的问题,则更容易获得更具体的答案。
不久前,我测量了Xenon PowerPC上直接和虚拟C ++函数调用的开销。
所讨论的函数具有单个参数和单个返回,因此参数传递发生在寄存器上。
长话短说,与内联函数调用相比,直接(非虚拟)函数调用的开销约为5.5纳秒,即18个时钟周期。与内联相比,虚拟函数调用的开销为13.2纳秒,即42个时钟周期。
这些时序在不同的处理器系列上可能有所不同。我的测试代码在这里;您可以在硬件上运行相同的实验。在CFastTimer实现中使用rdtsc这样的高精度计时器。系统时间()不够精确。