很多文献都讨论了使用内联函数来"避免函数调用的开销".但是我还没有看到可量化的数据.函数调用的实际开销是多少,即通过内联函数实现什么样的性能提升?
在大多数体系结构中,成本包括将所有(或部分或全部)寄存器保存到堆栈,将函数参数推送到堆栈(或将它们放入寄存器中),递增堆栈指针并跳转到堆栈的开头.新代码.然后,当函数完成时,您必须从堆栈中恢复寄存器. 该网页描述了各种调用约定中涉及的内容.
现在,大多数C++编译器都足够聪明,可以为您内联函数.inline关键字只是编译器的提示.有些人甚至会在翻译单元中进行内联,他们认为这些单元很有帮助.
有技术和实际的答案.实际的答案是它永远不会重要,并且在非常罕见的情况下,它通过实际的异形测试来确定唯一的方法.
由于编译器优化,您的文献所引用的技术答案通常不相关.但是,如果你仍然感兴趣,乔什很好地描述了.
至于"百分比",你必须知道功能本身有多昂贵.在被调用函数的成本之外没有百分比,因为您正在与零成本操作进行比较.对于内联代码没有成本,处理器只是移动到下一条指令.inling的缺点是更大的代码大小,这表明它的成本与堆栈构造/拆除成本不同.
我针对简单的增量函数做了一个简单的基准测试:
inc.c:
typedef unsigned long ulong; ulong inc(ulong x){ return x+1; }
main.c中
#include#include typedef unsigned long ulong; #ifdef EXTERN ulong inc(ulong); #else static inline ulong inc(ulong x){ return x+1; } #endif int main(int argc, char** argv){ if (argc < 1+1) return 1; ulong i, sum = 0, cnt; cnt = atoi(argv[1]); for(i=0;i 在我的英特尔(R)酷睿(TM)i5 CPU M 430 @ 2.27GHz上运行十亿次迭代,这让我:
内联版本为1.4秒
定期链接版本为4.4秒
(它似乎波动到0.2,但我懒得计算适当的标准偏差,我也不关心它们)
这表明此计算机上函数调用的开销约为3纳秒
我测量的最快速度大约为0.3ns,因此建议函数调用成本大约为9个原始操作,这非常简单.
对于通过PLT调用的函数(共享库中的函数),此开销每次调用大约增加2ns(总时间调用时间约为6ns).
4> Mark Ransom..:开销量取决于编译器,CPU等.开销百分比取决于您内联的代码.要知道的唯一方法是采用您的代码并对其进行两种分析 - 这就是为什么没有明确的答案.
5> Mecki..:你的问题是其中一个问题,没有人可以称之为"绝对真理".正常函数调用的开销取决于三个因素:
CPU.x86,PPC和ARM CPU的开销变化很大,即使您只使用一种架构,英特尔奔腾4,英特尔酷睿2双核和英特尔酷睿i7之间的开销也会有很大差异.即使两者都以相同的时钟速度运行,英特尔和AMD CPU之间的开销也可能明显不同,因为缓存大小,缓存算法,内存访问模式以及调用操作码本身的实际硬件实现等因素可能会产生巨大的影响.影响开销.
ABI(应用程序二进制接口).即使使用相同的CPU,也经常存在不同的ABI,它们指定函数调用如何传递参数(通过寄存器,通过堆栈或两者的组合)以及堆栈帧初始化和清理的位置和方式.所有这些都会对开销产生影响.不同的操作系统可能对同一CPU使用不同的ABI; 例如,Linux,Windows和Solaris可能都会为同一个CPU使用不同的ABI.
编译器.严格遵循ABI仅在独立代码单元之间调用函数时才重要,例如,如果应用程序调用系统库的函数或用户库调用另一个用户库的函数.只要函数是"私有的",在某个库或二进制文件之外不可见,编译器就可能"作弊".它可能不严格遵循ABI,而是使用导致更快的函数调用的快捷方式.例如,它可以在寄存器中传递参数而不是使用堆栈,或者它可以跳过堆栈帧设置并且如果不是真的必要则完全清理.
如果你想知道上述三个因素的特定组合的开销,例如对于使用GCC的Linux上的Intel Core i5,获取此信息的唯一方法是对两个实现之间的差异进行基准测试,一个使用函数调用,另一个使用将代码直接复制到调用者; 这样你就可以强制内联,因为内联语句只是一个提示,并不总是导致内联.
但是,真正的问题是:确切的开销真的很重要吗?有一件事是肯定的:函数调用总是有开销.它可能很小,可能很大,但肯定存在.而且,如果在性能关键部分中经常调用一个函数有多小,那么开销在某种程度上就很重要.内联很少会使你的代码变慢,除非你非常过分; 它会使代码更大.今天的编译器非常擅长决定自己什么时候进行内联,什么时候不进行内联,所以你几乎不用担心它.
我个人完全忽略了在开发过程中的内联,直到我有一个或多或少可用的产品,我可以分析,只有当分析告诉我,某个功能经常被调用,并且也在应用程序的性能关键部分,然后我会考虑这个功能的"强制内联".
到目前为止,我的答案非常通用,它适用于C,因为它适用于C++和Objective-C.作为结束语让我特别谈到C++:虚拟方法是双重间接函数调用,这意味着它们比正常函数调用具有更高的函数调用开销,并且它们也不能内联.非虚拟方法可能由编译器内联或不内联,但即使它们没有内联,它们仍然比虚拟方法快得多,因此您不应该使方法成为虚拟方法,除非您真的打算覆盖它们或覆盖它们.
6> Don Neufeld..:对于非常小的函数,内联是有意义的,因为函数调用的(很小)开销相对于函数体的(很小)开销是很大的。对于大多数功能而言,几行都不是什么大事。