代码之家  ›  专栏  ›  技术社区  ›  deft_code

为什么不把所有的东西都标成一行?

  •  45
  • deft_code  · 技术社区  · 15 年前

    首先,我是 寻找一种强制编译器内联每个函数的实现的方法。

    为了降低错误答案的水平,请确保您了解 inline 关键词实际上意味着。这是很好的描述, inline vs static vs extern .

    所以我的问题是,为什么不标记每个函数的定义呢? 内联的 ?ie理想情况下,唯一的编译单元是 main.cpp . 或者对于不能在头文件中定义的函数(pimpl-adily等)可能还有更多。

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

    有人用一个真正的应用程序试过这个吗?性能提高了吗?减少????

    标记所有函数定义的缺点是什么 内联的 ?

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

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

    11 回复  |  直到 10 年前
        1
  •  24
  •   Ben Voigt    15 年前

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

    实际上,微软的Visual C++在使用 /GL (Whole Program Optimization) switch 在链接器运行并访问所有代码之前,它实际上不会编译任何东西。其他编译器也有类似的选项。

        2
  •  15
  •   pm100    15 年前

    sqlite使用了这个想法。在开发过程中,它使用传统的源代码结构。但对于实际使用,有一个巨大的C文件(112K行)。他们这样做是为了最大限度地优化。索赔约5-10%的性能改进

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

        3
  •  9
  •   Crashworks    15 年前

    我们(和其他一些游戏公司)通过制作一个uber-.cpp来尝试它。 #include Ed所有其他人;这是一种已知的技术。在我们的例子中,它似乎对运行时没有太大的影响,但是您提到的编译时缺点最终是非常严重的。在每次更改后编译半小时后,就不可能有效地迭代了。(这个应用程序分成十几个不同的库。)

    我们尝试进行不同的配置,以便在调试时有多个.obj,然后只有uber cpp在opt版本中,但随后遇到的问题是编译器内存不足。对于一个足够大的应用程序,这些工具根本无法编译数百万行的cpp文件。

    我们也尝试了LTCG,这提供了一个小的但很好的运行时增强,在很少的情况下,它不会在链接阶段崩溃。

        4
  •  7
  •   Nick    15 年前

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

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

        5
  •  7
  •   James Eichele Bernard Igiri    15 年前

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

    我将从一个“过早优化”的角度来看待这个问题:多个文件中的模块化代码使程序员的生活更容易,所以这样做显然有好处。只有当一个特定的应用程序运行得太慢,并且可以显示出,嵌入所有东西都会有一定的改进时,我会不会 考虑 给开发者带来不便。即使在那时,它也将是在大多数开发已经完成(这样它就可以被测量)之后,并且可能只会在生产构建中完成。

        6
  •  3
  •   Community Mohan Dere    9 年前

    在某些情况下已经完成了。这与 unity builds 你所说的优点和缺点不是来自于你所描述的:

    • 编译器优化的潜力更大
    • 链接时间基本上消失了(如果所有内容都在一个翻译单元中,那么就没有什么可链接的了,真的)
    • 编译时间过得差不多。正如您提到的,增量构建变得不可能。另一方面,一个完整的构建将比它要快(因为每一行代码都只编译一次)。在常规构建中,头中的代码最终在包含头的每个翻译单元中编译)

    但是,如果您已经有很多只包含头部的代码(例如,如果您使用了大量的boost),那么从构建时间和可执行性能方面来说,这可能是非常值得优化的。

    尽管如此,当涉及到性能时,这取决于性能。这不是个坏主意,但也不是普遍适用的。

    就buld时间而言,基本上有两种优化方法:

    • 最小化翻译单元的数量(以便标题包含在较少的位置),或者
    • 最小化标题中的代码量(以便将标题包含在多个翻译单元中的成本降低)

    C代码通常采用第二个选项,非常极端:除了前向声明和宏以外,几乎没有其他选项保留在头中。 C++经常位于中间,这是你得到最坏可能的总构建时间的地方(但是PCH和/或增量构建可能会再次减少它的时间),但是在另一个方向上进一步前进,尽量减少翻译单元的数量确实可以为总构建时间带来奇迹。

        7
  •  3
  •   peterchen    14 年前

    没有什么好处 在现代平台的优秀编译器上, inline 只影响很少的功能。只是一个 暗示 对于编译器来说,现代编译器本身非常擅长做这个决定,并且函数调用的开销变得相当小(通常,内联的主要好处不是减少调用开销,而是打开进一步的优化)。

    编译时间 但是,由于inline也会改变语义,因此必须 #include 所有东西都集中在一个巨大的编译单元中。这个 通常 大大增加了编译时间,这在大型项目中是一个杀手。

    代码大小
    如果您不再使用当前的桌面平台及其高性能编译器,情况会发生很大变化。在这种情况下,由一个不太聪明的编译器生成的代码大小的增加将是一个问题——太多了,它会使代码明显变慢。在嵌入式平台上,代码大小通常是第一个限制。

    不过,有些项目可以而且确实从“内联一切”中获利。它提供了与链接时间优化相同的效果,至少如果编译器没有盲目地遵循 内联的 .

        8
  •  2
  •   Joe Valenzuela    15 年前

    这基本上就是背后的哲学 Whole Program Optimization 和链接时间代码生成(LTCG):优化机会是最好的全球知识。

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

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

        9
  •  2
  •   old_timer    15 年前

    这里的假设是编译器不能跨函数进行优化。这是特定编译器的限制,而不是一般问题。将此作为特定问题的一般解决方案可能是不好的。编译器可能会很好地用在其他地方编译的相同内存地址(使用缓存)的可重用函数(由于缓存而失去性能)来膨胀程序。

    大函数在一般优化代价上,存在着局部变量的开销与函数中的代码量之间的平衡。将函数中的变量数(传入、本地和全局变量)保持在平台的可释放变量数内会导致大多数内容都能够保留在寄存器中,而不必被逐出RAM,也不需要堆栈帧(取决于目标)。因此,函数调用开销显著减少。在现实世界的应用程序中一直很难做到,但另一种选择是,少量带有大量局部变量的大函数代码将花费大量时间将寄存器与变量从RAM中移出和加载(取决于目标)。

    尝试llvm,它可以在整个程序中进行优化,而不仅仅是一个函数一个函数的优化。版本27已经赶上了GCC的优化器,至少在一两个测试中,我没有做详尽的性能测试。28人出局了,所以我想会更好。即使有几个文件,调节旋钮组合的数量也太多了。我发现最好是在将整个程序放到一个文件中之前不要进行优化,然后执行优化,让优化器能够处理整个程序,基本上就是你想用内联来做什么,但是没有包袱。

        10
  •  1
  •   dshin    10 年前

    假设 foo() bar() 两个都叫一些 helper() . 如果所有内容都在一个编译单元中,编译器可能选择不内联 帮助者() ,以减少总指令大小。这导致了 英尺() 对进行非内联函数调用 帮助者() .

    编译器不知道对 英尺() 期望每天增加100美元。它不知道除了 英尺() 对你的底线没有影响。

    只有作为程序员的您知道这些事情(当然,在仔细分析和分析之后)。决定不插队 巴尔) 是告诉编译器你所知道的。

        11
  •  0
  •   nmichaels    15 年前

    内联的问题在于,您希望高性能函数适合缓存。您可能认为函数调用开销会对性能造成很大影响,但在许多体系结构中,一个缓存丢失会将这对夫妇推送并弹出水面。例如,如果您有一个很大(可能很深)的函数需要很少从主高性能路径调用,那么它可能会导致主高性能循环增长到不适合一级icache的程度。这将减慢代码的速度,比偶尔的函数调用要慢得多。