【问题标题】:Is reducing number of cpp translation units a good idea?减少 cpp 翻译单元的数量是个好主意吗?
【发布时间】:2023-03-16 09:31:01
【问题描述】:

我发现如果有很多类,当我为每个类使用一个 *.h 和一个 *.cpp 文件时,编译时间会显着增加。我已经使用了预编译头文件和增量链接,但是编译时间仍然很长(是的,我使用 boost ;)

于是我想出了以下技巧:

  • 将 *.cpp 文件定义为不可编译
  • 将 *.cxx 文件定义为可编译
  • 为每个应用程序添加了一个 *.cxx 文件 模块,并在其中#include了该模块的所有*.cpp文件。

因此,我最终只得到了 8 个翻译单元,而不是 100 多个翻译单元。编译时间缩短了 4-5 倍。

缺点是您必须手动包含所有 *.cpp 文件(但这并不是真正的维护噩梦,因为如果您忘记包含链接器会提醒您的内容),并且某些 VS IDE 便利性无法使用这个方案,例如转到/移至实施等。

所以问题是,拥有大量 cpp 翻译单元真的是唯一正确的方法吗?我的把戏是已知的模式,还是我遗漏了什么? 谢谢!

【问题讨论】:

    标签: c++ performance build-process module header-files


    【解决方案1】:

    这种方法的一个显着缺点是每个翻译单元都有一个 .obj 文件。

    如果您创建一个静态库以便在其他项目中重用,如果您有多个大型翻译单元而不是许多小型翻译单元,那么您通常会在这些项目中遇到更大的二进制文件,因为链接器只会包含包含函数/变量的 .obj 文件真正在使用该库的项目中引用。

    如果翻译单元较大,则更有可能引用每个单元并包含相应的 .obj 文件。在某些情况下,更大的二进制文件可能会成为问题。也有可能一些链接器足够聪明,只包含必要的函数/变量,而不是整个 .obj 文件。

    此外,如果包含 .obj 文件并包含所有全局变量,则它们的构造函数/析构函数将在程序启动/停止时被调用,这肯定需要时间。

    【讨论】:

    • Visual Studio 支持函数级链接。不过,它仍然适用于 Unix/Linux。
    • 函数级链接是否也只排除函数或变量?
    【解决方案2】:

    我已经看到了您在视频游戏中所做的事情,因为它可以帮助编译器进行优化,否则它无法做到以及节省大量内存。我见过“超级构建”和“批量构建”指的是这个想法。如果它有助于加快构建速度,何乐而不为..

    【讨论】:

      【解决方案3】:

      将大量 C++ 源代码文件捆绑到一个文件中是最近多次提到的一种方法,尤其是当人们构建大型系统并引入复杂的头文件时(那将是提升) .

      正如你提到的 VS,我发现项目中包含文件的数量,尤其是包含路径的大小,似乎对 Visual C++ 的编译时间的影响远远超过它对 g++ 的编译时间的影响。对于大量嵌套包含(同样,boost 会这样做)尤其如此,因为需要大量文件搜索才能找到源代码所需的所有包含文件。将代码组合到单个源文件中意味着编译器可以更智能地查找所述包含,而且显然可以找到更少的包含,因为您期望同一子项目中的文件可能包含非常相似的一组头文件。

      C++ 开发的“大量编译单元​​”方法通常来自于解耦类和最小化类之间的依赖关系的愿望,因此编译器只需重建最小的文件集以防您进行任何更改。这通常是一种很好的方法,但在子项目中通常不太可行,因为其中的文件相互依赖,因此无论如何您最终都会进行相当大的重建。

      【讨论】:

        【解决方案4】:

        我不认为减少编译单元的数量是个好主意。您正在尝试解决编译时间较长的问题,这种方法似乎对此有所帮助,但是您还得到了什么:

        1. 在开发过程中增加了编译时间。通常开发人员一次修改几个文件,编译 3-4 个小文件可能比编译一个非常大的文件更快。
        2. 正如您所提到的,更难导航代码,恕我直言,这非常重要。
        3. 您可能会在包含在一个 .cxx 文件中的 .cpp 文件之间产生一些干扰:

          一个。通常的做法是在 cpp 文件(用于调试构建)中本地定义宏 new 以进行内存泄漏检查。不幸的是,在使用新位置包含标头之前无法完成此操作(就像某些 STL 和 BOOST 标头所做的那样)

          b.在 cpp 文件中添加 using 声明是常见的做法。使用您的方法,这可能会导致标题出现问题,稍后包含

          c。名称冲突的可能性更大

        恕我直言,加快编译时间的更简洁(但可能更昂贵的方法)是使用一些分布式构建系统。它们对于干净的构建特别有效。

        【讨论】:

          【解决方案5】:

          我不确定这是否与您的情况相关,但也许您可以使用声明而不是定义来减少您必须做的#include 的数量。此外,也许您可​​以将 pimpl 成语用于相同目的。这有望减少每次需要重新编译的源文件数量以及必须拉入的头文件数量。

          【讨论】:

            【解决方案6】:

            这个概念叫做unity build

            【讨论】:

              【解决方案7】:

              更大和更少的翻译单元不会利用并行编译。我不知道您使用的是什么编译器和什么平台,但是并行编译多个翻译单元可能会显着减少构建时间...

              【讨论】:

              • 这不一定是真的 - OP 提到他正在使用 Visual Studio,如果您添加适当的命令行参数,最新版本可以使用多个处理器来编译单个源文件。
              • 不保证加速是一样的。知道如何使用多个线程编译单个源文件是一个难题,我怀疑它是否与能够通过多个单独的编译分发构建一样有效。
              【解决方案8】:

              继Sharptooths 帖子之后,我倾向于详细检查生成的可执行文件。如果它们不同,我倾向于将您的技术限制为调试构建并使用原始项目配置来进行主发布构建。在检查可执行文件时,我还会查看它在启动和运行时的内存占用和资源使用情况。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2019-09-20
                • 1970-01-01
                • 1970-01-01
                • 2010-11-05
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多