为什么不标记所有内联?
首先,我不想要强制编译器内联每个函数的实现。
为了减less误导的答案的水平,请确保您了解inline
关键字的实际含义。 这里是很好的描述, 内联vs静态vs外部 。
所以我的问题,为什么不标记每个函数定义inline
? 理想情况下,唯一的编译单元是main.cpp
。 或者可能还有一些不能在头文件中定义的函数(pimpl idiom等)。
这个奇怪的请求背后的理论是它会给优化器最大的信息工作。 它当然可以内联函数实现,但是它也可以做“跨模块”优化,因为只有一个模块。 还有其他优点吗?
有没有人用真正的应用程序试过这个? 表演增加了吗? 减less?!?
inline
标记所有函数定义有什么缺点?
- 编译可能会更慢,会消耗更多的内存。
- 迭代构build被破坏,整个应用程序将需要在每次更改后重build。
- 链接时间可能是天文数字
所有这些缺点只会影响开发者。 什么是运行时间的缺点?
你真的是说#include
一切吗? 这将只给你一个单一的模块,并让优化器一次看到整个程序。
实际上,当你使用/GL
(整体程序优化)开关时,微软的Visual C ++就完全实现了 ,直到链接器运行并且可以访问所有的代码,它才会真正编译任何东西。 其他编译器也有类似的选项。
sqlite使用这个想法。 在开发过程中,它使用传统的源结构。 但实际使用有一个巨大的c文件(112k线)。 他们这样做最大限度的优化。 声称性能提升约5-10%
我们(和其他一些游戏公司)通过制作一个超级用户来进行尝试。 这是一个已知的技术。 在我们的例子中,它似乎并没有对运行时造成太大的影响,但是你提到的编译时间的缺点却变成了极度的瘫痪。 在每次更改之后,只需半小时编译一次,就不可能有效地进行迭代。 (这是应用程序分成了十几个不同的图书馆。)
我们试着做了一个不同的configuration,在debugging的时候我们会有多个.objs,然后只有在release-opt版本中才有uber-CPP,但是却遇到了编译器的问题,只是内存不足。 对于一个足够大的应用程序来说,这些工具根本无法编译一个数百万行的cpp文件。
我们也尝试了LTCG,并且提供了一个很小但很好的运行时间提升,在极less数情况下它不会在链接阶段崩溃。
这是半相关的,但请注意,Visual C ++确实有跨模块优化的能力,包括跨模块内联。 有关信息,请参阅http://msdn.microsoft.com/en-us/library/0zza0de8%28VS.80%29.aspx 。
为了给原来的问题添加一个答案,假设优化器足够聪明(因此为什么它被添加为Visual Studio中的优化选项),我不认为在运行时会有负面影响。 只要使用一个足够聪明的编译器就可以自动完成,而不会造成所有你提到的问题。 🙂
有趣的问题! 所有列出的缺点都是特定于开发人员的。 然而,我会build议一个处于不利地位的开发者生产高质量产品的可能性要小得多。 可能没有运行时间的缺点,但想象一下,如果每个编译需要几个小时(甚至几天)才能完成,那么开发人员将不会做出微小的更改。
我会从“过早优化”的angular度来看待这个问题:多个文件中的模块化代码使得程序员的生活变得更容易,所以这样做有明显的好处。 只有当一个特定的应用程序运行速度太慢,并且可以表明,内联一切都会使衡量的改进,我甚至会考虑给开发人员带来麻烦。 即使这样,在大部分开发工作完成之后(这样才能测量),也许只能用于生产build设。
已经在某些情况下完成了。 这与团结build设的想法非常相似,优点和缺点都不符合你的愿望:
- 更多的编译器优化的潜力
- 链接时间基本消失(如果一切都在一个单一的翻译单位,没有什么可以链接,真的)
- 编译时间,以及其他方式。 正如你所提到的,增量构build变得不可能。 另一方面,一个完整的构build会比其他方式更快(因为每行代码都只编译一次)。在常规构build中,标题中的代码最终被编译到包含标题的每个翻译单元中)
但是如果你已经有很多只有头文件的代码(例如,如果你使用了很多的Boost),那么在编译时间和可执行性能方面,这可能是一个非常有价值的优化。
与往常一样,当涉及到表演时,这取决于。 这不是一个坏主意,但也不是普遍适用的。
至于buld时间,你基本上有两种方法来优化它:
- 尽量减less翻译单元的数量(所以你的标题包含在更less的地方),或者
- 最小化报头中的代码量(这样在多个翻译单元中包括报头的成本降低)
C代码通常采用第二种select,几乎是极端的:除了前面的声明和macros之外,几乎没有任何内容保存在头文件中。 C ++通常位于中间,这是你得到最糟糕的总构build时间的地方(但PCH和/或增量构build可能会再次减less一些时间),但是在另一个方向上进一步减less翻译单元的数量真的不知道总的build造时间。
一点好处对于现代平台的一个好的编译器, inline
只会影响很less的函数。 这只是编译器的一个暗示 ,现代编译器本身就很擅长做这个决定,函数调用的开销也变得相当小(通常,内联的主要好处不是减less调用开销,而是开放进一步优化)。
编译时间然而,由于内联也会改变语义,所以你将不得不将所有内容都包含在一个巨大的编译单元中。 这通常会大大增加编译时间,这是大型项目的杀手锏。
代码大小
如果您从目前的桌面平台和高性能编译器中移走,事情就会发生很大的变化。 在这种情况下,由不太聪明的编译器生成的代码大小增加将成为一个问题 – 太多了,使代码显着变慢。 在embedded式平台上,代码大小通常是第一个限制。
尽pipe如此,一些项目可以通过“内联一切”来获利。 它给你链接时间优化相同的效果,至less如果你的编译器不盲目地遵循inline
。
这就是全程序优化和链接时间代码生成(LTCG)背后的理念:在全球知识的帮助下,优化机会是最好的。
从实际的angular度来看,这是一种痛苦,因为现在每一个改变都需要重新编译整个源码树。 一般来说,你需要一个经过优化的构build,而不需要频繁地进行任意的修改。
我在Metrowerks时代尝试了这个(使用“Unity”风格的构build很容易),编译也没有完成。 我提到它只是指出,这是一个工作stream程设置,可能会以不希望的方式对工具链征税。
这里的假设是编译器不能跨function进行优化。 这是特定编译器的限制,而不是一个普遍的问题。 使用这个作为一个特定问题的一般解决scheme可能是不好的。 编译器可能只是膨胀你的程序,在相同的内存地址(获取使用caching)可以重用的函数在其他地方被编译(并因为caching而失去性能)。
一般来说,大的函数花费在优化上,在局部variables的开销和函数中的代码量之间是平衡的。 保持函数中的variables数量(包括传入的,本地的和全局的)到平台的一次性variables的数量之内,这样大多数的东西都能够保留在寄存器中而不必被驱逐到内存中,帧是不需要的(取决于目标),所以函数调用开销显着减less。 很难在现实世界中的应用程序,但替代less数大function与大量的本地variables代码将花费大量的时间驱逐和加载寄存器variables/从RAM(取决于目标)。
试试llvm它可以在整个程序中进行优化,而不仅仅是按function运行。 第27版已经赶上海湾合作委员会的优化,至less有一两个testing,我没有做详尽的性能testing。 而28,所以我认为这是更好的。 即使只有几个文件,调音旋钮组合的数量也太多了。 我发现最好不要优化,直到你把整个程序整合到一个文件中,然后执行你的优化,给优化器整个程序工作,基本上你正在用内联来做什么,但没有行李。
内联的问题是你想要高性能函数来适应caching。 你可能会认为函数调用的开销是大的performance,但是在许多架构中,caching的错误将会使这对夫妇推动并跳出水面。 例如,如果您的主要高性能path中需要很less调用大型(可能是深度)函数,则可能会导致主要高性能循环增长到不适合L1 icache的程度。 这会减慢代码的速度,而不是偶尔的函数调用。
假设foo()
和bar()
都调用一些helper()
。 如果一切都在一个编译单元中,编译器可能会select不内联helper()
,以减less总的指令大小。 这会导致foo()
对helper()
进行非内联函数调用。
编译器并不知道对foo()
的运行时间进行的纳秒改进在预期的基础上增加了$ 100 /天。 它不知道foo()
以外的任何性能改进或降级对您的底线没有影响。
只有你作为程序员知道这些事情(经过仔细的分析和分析当然)。 不要内联bar()
是一种告诉编译器你知道的方法。