首先,我是 不是 寻找一种迫使编译器嵌入每个函数的实现的方法。

为了降低错误的答案水平,请确保您了解 inline 关键字实际上是指。这是很好的描述, 内联与静态与外部.

所以我的问题,为什么不标记每个功能定义 inline?即理想情况下,唯一的汇编单元是 main.cpp. 。或可能在标题文件中无法定义的功能(PIMPL IDIOM等)。

这个奇数请求背后的理论是,它将为优化器提供最大的信息。当然,它可以内联函数实现,但也可以进行“交叉模块”优化,因为只有一个模块。还有其他优势吗?

有没有人尝试使用真实的应用程序?性能增加了吗?减少?!?

标记所有功能定义的缺点是什么 inline?

  • 汇编可能会较慢,并且会消耗更多的内存。
  • 迭代构建被打破,每次更改后都需要重建整个应用程序。
  • 链接时间可能是天文学的

所有这些缺点仅影响开发人员。运行时缺点是什么?

有帮助吗?

解决方案

你真的是说 #include 一切?这只会给您一个单个模块,并让优化器一次查看整个程序。

实际上,Microsoft的Visual C ++在您使用时会做到这一点 /GL (整个程序优化)开关, ,直到链接器运行并可以访问所有代码之前,它实际上不会编译任何内容。其他编译器也有类似的选项。

其他提示

Sqlite使用了这个想法。在开发过程中,它使用传统的源结构。但是实际使用有一个巨大的C文件(112k行)。他们这样做是为了最大程度的优化。要求提高约5-10%

http://www.sqlite.org/amalgamation.html

我们(和其他一些游戏公司)确实通过制作一个Uber-.cpp尝试了一下 #include其他所有人;这是一种已知的技术。在我们的情况下,它似乎并没有太大影响运行时,但是您提到的编译时间的缺点完全是残酷的。每次更改后半个小时编译半小时,就无法有效迭代。 (这是因为该应用程序分为十几个不同的库。)

我们尝试制作不同的配置,以使我们在调试时会有多个.OBJ,然后仅在realease-opt构建中具有UBER-CPP,但随后遇到了编译器的问题,只是简单地用完了内存。对于一个足够大的应用程序,这些工具根本无法编译数百万线CPP文件。

我们也尝试了LTCG,并提供了一个很小但不错的运行时提升,在极少数情况下,它不仅在链接阶段崩溃。

有趣的问题!当然,您是正确的,所有列出的缺点都是特定于开发人员的。但是,我建议一个处境不利的开发人员生产优质产品的可能性要小得多。可能没有运行时的缺点,但是想象一下,如果每个编译需要数小时(甚至几天)才能完成,开发人员将多么不愿进行小小的更改。

我会从“过早优化”角度来看这一点:多个文件中的模块化代码使程序员的生活更轻松,因此,以这种方式进行操作有明显的好处。只有当特定的应用程序运行太慢时,可以证明,将所有内容融入所有内容都可以得到测量,我甚至会 考虑 给开发商带来不便。即使那样,在大部分开发已经完成(可以进行衡量)之后,可能只能用于生产制造。

这是半相关的,但请注意,Visual C ++确实具有进行跨模块优化的能力,包括跨模块的内联。看 http://msdn.microsoft.com/en-us/library/0zza0de8%28vs.80%29.aspx 有关信息。

为了添加您最初的问题的答案,我认为在运行时不会有缺点,假设优化器足够聪明(因此,为什么在Visual Studio中添加了它作为优化选项)。只需使用足够智能的编译器即可自动执行此操作,而不会创建您提到的所有问题。 :)

几乎没有好处在现代平台的好编译器上, inline 将仅影响极少数功能。这只是一个 暗示 对于编译器而言,现代编译器本身相当擅长做出此决定,并且功能呼叫的开销变得很小(通常,内部嵌入的主要好处是不是减少呼叫开销,而是开放进一步的优化)。

编译时间 但是,由于内联还会改变语义,您将不得不 #include 一切都变成一个巨大的编译单元。这个 通常 大幅度增加了编译时间,这是大型项目的杀手。

代码大小
如果您远离当前的桌面平台及其高性能编译器,情况会发生很多变化。在这种情况下,不太聪明的编译器生成的增加代码大小将是一个问题 - 如此之多,以至于它使代码大大降低。在嵌入式平台上,代码大小通常是第一个限制。

尽管如此,某些项目仍可以从“内联”中获利。它给您与链接时间优化相同的效果,至少如果您的编译器不盲目遵循 inline.

在某些情况下已经完成。这与想法非常相似 团结建立, ,而且优势和缺点不是您的descibe的FA:

  • 编译器优化的更多潜力
  • 链接时间基本上消失了(如果一切都在单个翻译单元中,那么实际上没有什么可链接的)
  • 编译时间是一种或另一种方式。正如您提到的那样,增量构建变得不可能。另一方面,完整的构建将比其他构建更快(因为每行代码都经过了一次编译。 )

但是,如果您已经拥有大量仅标头代码(例如,如果您使用了很多提升),那么在构建时间和可执行性能方面,这可能是非常有价值的优化。

与往常一样,当涉及性能时,这取决于。这不是一个坏主意,但也不是普遍适用的。

就大整个时间而言,您基本上有两种优化它的方法:

  • 最小化翻译单元的数量(因此,您的标题包含在更少的地方)或
  • 最小化标头中的代码量(以便在多个翻译单元中包含标头的成本减少)

C代码通常采用第二种选择,几乎是极端的:除了向前声明和宏位于标题中,几乎没有任何选择。 C ++通常位于中间,这是您获得最糟糕的总构建时间(但是PCH和/或增量构建可能会再次休假),但是朝另一个方向走得更远,最小化翻译单元的数量可以确实为整个构建时间带来了奇迹。

那几乎是背后的哲学 整个程序优化 和链接时间代码生成(LTCG):优化机会是最好的。

从实际的角度来看,这有点痛苦,因为现在您所做的每一个更改都需要重新编译整个源树。一般而言,您需要的优化构建要比进行任意更改所需的频率少。

我在Metrowerks时代尝试了此操作(使用“ Unity”样式构建非常容易设置),并且编译从未完成。我提到的只是指出,这是一个工作流程设置,可能会以他们没有预期的方式对工具链征税。

这里的假设是编译器无法跨函数进行优化。这是特定编译器的限制,而不是一般问题。将其用作特定问题的一般解决方案可能是不好的。编译器很可能只是在相同的内存地址(使用缓存)在其他地方编译的相同内存地址的可重复使用功能(并且由于缓存而失去性能)。

一般成本的优化功能,局部变量的开销与函数中的代码量之间存在平衡。将函数中的变量数(均传递,本地和全局)保持在平台的一次性变量次数之内,从而导致大多数所有能够留在寄存器中,而不必驱逐出RAM,也可以堆栈不需要框架(取决于目标),因此明显降低了调用开销的功能。在现实世界中,很难在现实世界应用中执行,但是替代方案少数具有许多本地变量的大函数将花费大量时间驱逐和加载寄存器,并加载寄存器,并从RAM到/来自RAM(取决于该变量(取决于)目标)。

尝试LLVM,它可以在整个程序中优化整个程序,而不仅仅是函数函数。第27版已经赶上了GCC的优化器,至少在一两次测试中,我没有进行详尽的性能测试。 28已经出来了,所以我认为更好。即使有几个文件,调谐旋钮组合的数量也太多了。我发现最好不要在将整个程序放入一个文件中,然后执行优化,从而使Optimizer提供整个程序可以使用,但基本上是您试图使用内部的工作,但没有行李,最好不要进行优化。

认为 foo()bar() 两者都打电话 helper(). 。如果一切都在一个编译单元中,则编译器可能会选择不嵌入式 helper(), ,为了减少总指令大小。这引起 foo() 为了进行非插入函数调用 helper().

编译器不知道纳秒的运行时间有所改善 foo() 预期为您的底线增加$ 100。它不知道表现的改善或退化 foo() 对您的底线没有影响。

只有您作为程序员才知道这些事情(当然,经过仔细的分析和分析之后)。决定不介入 bar() 是告诉编译器您所知道的一种方式。

内衬的问题在于,您希望高性能功能适合缓存。您可能会认为功能呼叫开销是最大的性能,但是在许多体系结构中,缓存小姐会炸毁这对夫妻的推动,然后从水中弹出。例如,如果您的功能很大(可能是深)功能很少从主要的高性能路径中调用,则可能导致您的主要高性能循环成长到不适合L1 ICACHE的点。这将使您的代码降低,而不是偶尔的函数调用。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top