/*此文是译者出于自娱翻译的GotW(Guru of the Week)系列文章的一篇,原文的版权是属于Hub Sutter(著名的C++专家,《Exceptional C++》的作者)。此文的翻译没有征得原作者的同意,只供学习讨论。——译者:杨威远,黄森堂*/
#33 内联.
难度:4/10
与通常的看法相反,内联并不是一个有魔力的字眼。然而,当你适当的使用它的时候,它是一个有用的工具。问题是,我们什么时候应该使用它?
问题:
1.使用内联函数增加效率吗?
当你决定使用内联函数的时候该如何做?
解决方法:
1.使用内联函数增加效率吗?
不一定增加效率。
首先,如果你直接回答这个问题而没有问一下你自已:“我想最优化什么”,你就会落入一个经典的圈套。首先应该问一下:“你的效率指的是什么?”上面的问题指的是程序的大小?内存占用大小?运行速度?开发速度?编译速度?或者其他的什么?
第二,与通常的意见相反,内联会提高或降低效率:
a) 程序的大小。许多程序员认为内联会增大程序的大小,因为取代只有一份的函数编码,编译器在每个使用函数的地方创建一份拷贝。这通常来说是正确的,但并不总是正确:如果函数的大小比编译器必须生成的用来实现函数调用的代码小,那么内联会减小程序的大小。
b) 内存占用大小:除了程序的大小(上面所说的),内联通常对程序的内存使用只有很小或根本没有影响。
c) 运行速度:许多程序员认为内联函数会增加运行的时间,因为它消除了对函数的调用,并且因为通过掩盖函数调用来给编译最更多的优化机会。这可以说是正确的,但很多时候不是:如果函数不是频繁地被调用,这通常对于整个程序的运行速度并没有显著的提高。实际上,与之相反的情况会发生:如果内联增加了调用函数的大小,它会减少调用程序时间,这意味着如果调用程序内循环不放在处理器的cache里面话,整个程序的速度事实上变得更坏。
c)执行时间。 许多程序员推测内嵌函数会改善执行速度,因为避免了函数调用的开销而且透过了函数调用的"面罩来看"编译器的优化层有了更大的用武之地。 有时是这样, 不过常常不是。如果函数没有极其频繁的被调用,一般看不出程序整体执行时间的改观。 实际上, 相反的情况很可能发生:如果内嵌增加了掉用其的函数的大小则相应的调用者的引用范围会减小,意味着在当处理器的高速缓存容不下调用者的内层循环了时,程序的整体执行速度实际上降低了。
把这点放到整个观点里面,不要忘记绝大多数程序并不受CPU限制。也许绝大多数共同的瓶颈是I/O限制,这包括网络带宽、文件或数据库处理的反应时间中的所有事情。
d) 开发速度,编译速度。最有用的是,内联的代码对于调用程序来说必须是可见的,这意味着调用程序必须依赖于内部的内联代码。依靠其他模块的内部执行的详细资料会增加实际的模块的联合(然而,它并不增加它们理论上的联合,因为调用程序事实上并不使用任何被调用程序的内部信息)。通常当function进行了改变,调用程序不需要进行重编译(只需要进行重新联结,并且有时候甚至连这个也不需要)。而当内联函数进行了改变,调用程序必须要进行重编译。
最后,如果你正在关注某种可以提高效率的方法,先看看你的算法和数据结构...他们会带给你数量级上的全面的提高,尽管,向内联一样的程序最优化一般(注意,“一般”)只会产生比较小的效果。
仅仅说“现在还不行”
2. 你应该在什么时候,用什么方法来内联一个函数?
就象其他的最优化:如果有一种分析器告诉你,用不了一分钟的时间。唯一的你需要立刻进行内联的时刻是当它是一个很可能保持为空的空函数,或者你绝对的被迫去这么做的时候,例如,当你写一个non-exported的模板的时候。
结果是,内联通常会占用某些东西,如果仅仅是增加联合,并且直到你知道你将会得到什么的时候才付出某些东西——也就是说,得到更好的回报。
“但我并不是总能够发现瓶颈是什么”你会不会这么想?不要着急,你并不孤单,因为绝大多数的程序员在同一时间或其他的时间里也在考虑这个问题,但你仍然是错的。完全的错了。程序员对于猜测他们代码中的真正的瓶颈所在是出了名的没有想象力。
通常只有试验的迹象(又叫做分析输出)能够帮助告诉你真正的紧迫的地方在哪里。十之八九,如果没有某种分析工具,程序员不能够确定他的代码中最紧迫的瓶颈的地点。我在这个领域研究了十年,我在和我工作过或听说过的所有的程序员的身上都发现这个同样的异常...即使每个人和他们年轻的同胞也许会声称这在他们身上不会出现直到他们被弄得脸色发青。:-)
[请注意另一个实际的原因:Profilers(分析器)在确定那个内联函数应该被内联方面作得并不好。
对于那些计算密集型的任务(比如数字[计算]库)会如何呢?
有些人喜欢写小的、紧凑的库代码,例如先进的科学和工程数字库,并且有时候能够做合适的内联。然而,那些开发员倾向于进行明智的内联并且倾向于更晚的进行调整。请注意,写一个模块,然后比较内联和不内联的性能通常来说不是个好主意,因为全部内联或全部不内联只是一个粗糙的衡量,只能告诉你平均的情况...它不能告诉你哪个函数可以得到好处(并且每一个得到多少)。即使在这些情况下,通常来说你最好还是使用个分析器,然后在它的建议的基础上优化你的程序。
读值器(Accessor)如何?
有人会说一行的读值器(Accessor)函数(如 X& Y::f(){return myX_;} )是一个合情合理的例外,可以/应该自动内嵌。我理解他们的思路,但要小心: 最起码全部内嵌的代码增加了耦合,因此除非你事先确知内嵌有益, 不妨等剖析后再作决定。 其后等代码稳定后,剖析器可能会指出内嵌有好处, 这时: a)你知道你所做的是值得的; 而且 b)你避免了项目开发周期结束前的所有的耦合和可能的编译消耗(指编译器为内嵌函数的每一次使用插入其全部代码这一过程的消耗--译注)。 这笔帐划得来,。。。
In Summary
From the GotW coding standards:
- avoid inlining or detailed tuning until performance profiles prove the need (Cline95: 139-140, 348-351; Meyers92: 107-110; Murray93: 234-235; 242-244)
- corollary: in general, avoid inlining (Lakos96: 631-632; Murray93: 242-244)
[Note that this deliberately says "avoid" (not "never") and that these same coding standards do encourage prior inlining in one or two very restricted situations.]
Cline95: Marshall Cline and Greg Lomow. "C++ FAQs" Addison-Wesley, 1995
Lakos96: John Lakos. "Large-Scale C++ Software Design" Addison-Wesley, 1996
Meyers92: Scott Meyers. "Effective C++" Addison-Wesley, 1992
Murray93: Robert Murray. "C++ Strategies and Tactics" Addison-Wesley, 1993