在C ++中调用函数有多少开销?
许多文献谈到使用内联函数来"避免函数调用的开销"。但是我还没有看到可量化的数据。函数调用的实际开销是多少,即通过内联函数可以实现什么样的性能提升?
解决方案
对于非常小的函数,内联是有意义的,因为函数调用的(很小)成本相对于函数主体的(很小)成本而言是巨大的。对于大多数功能而言,要花几行时间,这并不是一个大的胜利。
在大多数架构上,成本包括将所有(或者部分或者全部或者全部)寄存器保存到堆栈,将函数参数推入堆栈(或者将其放入寄存器),递增堆栈指针并跳转到堆栈的开头。新代码。然后,完成该功能后,我们必须从堆栈中还原寄存器。该网页描述了各种调用约定中涉及的内容。
大多数C ++编译器现在已经足够聪明,可以为我们内联函数。 inline关键字只是对编译器的提示。有些人甚至会在他们认为有用的地方跨翻译单位进行内联。
每个新功能都需要创建一个新的本地堆栈。但是,仅当我们在大量迭代中的每次循环迭代中调用一个函数时,此开销才是显而易见的。
对于大多数函数,在C ++与C中调用它们没有额外的开销(除非我们将" this"指针视为每个函数的不必要参数。我们必须以某种方式将状态传递给函数)...
对于虚函数,它们是间接的添加级别(等同于通过C中的指针调用函数)...但是,实际上,在当今的硬件上,这是微不足道的。
开销的数量将取决于编译器,CPU等。开销的百分比将取决于我们要内联的代码。唯一知道的方法是同时获取代码和配置文件,这就是为什么没有确切答案的原因。
我也没有任何电话,但我很高兴你在问。我经常看到人们尝试从模糊的开销想法开始优化代码,但并不是真的知道。
这里有一些问题。
- 如果我们有足够聪明的编译器,即使我们未指定内联,它也会为我们执行一些自动内联。另一方面,有很多事情不能内联。
- 如果该函数是虚函数,那么我们当然要付出无法内联的代价,因为目标是在运行时确定的。相反,在Java中,除非我们指出该方法是最终方法,否则我们可能会为此付出代价。
- 根据代码在内存中的组织方式,由于代码位于其他位置,我们可能要为高速缓存未命中甚至页面未命中付出代价。最终可能会对某些应用程序产生巨大影响。
有一个伟大的概念称为"寄存器影子",它允许通过寄存器(在CPU上)而不是堆栈(内存)传递(最多6个)值。另外,根据内部使用的功能和变量,编译器可能会决定不需要帧管理代码!
而且,即使C ++编译器也可能会执行"尾递归优化",即,如果A()调用B(),并且在调用B()之后,A刚刚返回,则编译器将重用堆栈帧!
当然,只有在程序遵循标准语义的情况下,所有这些都可以完成(请参阅指针别名及其对优化的影响)
有技术和实践的答案。实际的答案是,这将无关紧要,在极少数情况下,它唯一会通过实际的概要测试进行测试的方法。
由于编译器的优化,文献中提到的技术答案通常是不相关的。但是,如果我们仍然感兴趣,Josh会对它进行了很好的描述。
至于"百分比",我们必须知道该函数本身的昂贵程度。在被调用函数的成本之外,没有任何百分比,因为我们正在与零成本操作进行比较。对于内联代码没有成本,处理器仅移至下一条指令。内联的缺点是较大的代码大小,这表明其成本与堆栈构建/拆卸成本的方式不同。
值得指出的是,内联函数会增加调用函数的大小,任何增加函数大小的内容都可能会对缓存产生负面影响。如果我们恰好位于边界处,则内联代码"仅再薄薄一层"就可能对性能产生极大的负面影响。
如果我们正在阅读有关"函数调用成本"的警告的文献,我建议它可能是较旧的材料,无法反映现代处理器。除非我们处于嵌入式世界中,否则C是"便携式汇编语言"的时代已经过去了。在过去的十年中(例如),大量的芯片设计人员进入了各种低层复杂性,这些复杂性可能与"过去"的工作方式截然不同。