减少cpp翻译单元数量是一个好主意吗?

13
我发现,当我每个类使用一个*.h和一个*.cpp文件时,如果有很多类,编译时间会显著增加。我已经使用预编译头和增量链接,但编译时间仍然非常长(是的,我使用了boost;)。
因此,我想出了以下的技巧:
- 将*.cpp文件定义为不可编译文件 - 将*.cxx文件定义为可编译文件 - 每个应用程序模块添加一个*.cxx文件,并在其中#包含该模块的所有*.cpp文件。
所以,我最终只有8个翻译单元,而不是100多个翻译单元。编译时间缩短了4-5倍。
缺点是你必须手动包含所有*.cpp文件(但这不是真正的维护噩梦,因为如果你忘记包含某些内容,链接器会提醒你),并且一些VS IDE的便利性不适用于这种方案,例如“转到/移动到实现”等。
所以问题是,是否有很多cpp翻译单元确实是唯一正确的方法?我的技巧是一种已知的模式,还是我漏掉了什么?
谢谢!
8个回答

5
这种方法的一个显著缺点是每个翻译单元都需要一个.obj文件。如果您创建一个静态库以供其他项目重用,那么如果您有几个巨大的翻译单元而不是许多小的翻译单元,则在这些项目中会面临更大的二进制文件,因为链接器只会包含从使用库的项目内部引用的函数/变量的.obj文件。
对于大型翻译单元,更可能引用每个单元并包含相应的.obj文件。更大的二进制文件可能在某些情况下是一个问题。也有可能一些链接器足够聪明,只包括必要的函数/变量,而不是整个.obj文件。
此外,如果包含了.obj文件和所有全局变量,那么当程序启动/停止时将调用它们的构造函数/析构函数,这肯定需要时间。

Visual Studio支持函数级别的链接。虽然对于Unix/Linux仍然相关。 - MSalters
函数级别的链接是否仅排除函数,还是包括变量? - sharptooth

4

我看到你在视频游戏中所做的事情,因为它可以帮助编译器进行优化,否则无法完成,并且可以节省大量内存。我见过“超级构建”和“批量构建”这个概念。如果它可以加速您的构建,为什么不试试呢。


3
将大量的C++源代码文件捆绑成一个文件是一种最近提到过的方法,特别是当人们构建大型系统并引入复杂的头文件(那应该是boost)时。由于你提到了VS,我发现项目中包含的头文件数量,尤其是包含路径的大小,似乎对Visual C++的编译时间产生比g++更大的影响。这在有许多嵌套的包含文件(再次,boost就是这样)的情况下尤其如此,因为需要进行大量的文件搜索以找到源代码所需的所有包含文件。将代码合并到单个源文件中意味着编译器可以更加智能地查找这些包含文件,并且显然要找到的文件更少,因为您预计同一子项目中的文件可能会包括非常相似的一组头文件。
采用“大量编译单元”的方法来开发C++通常来自于希望解耦类并最小化类之间的依赖关系,以便编译器只需重新构建最小的文件集以防您进行任何更改。这通常是一个不错的方法,但在子项目中通常不太可行,因为其中的文件彼此具有依赖关系,因此您最终仍然会面临相当大的重建问题。

3
我认为减少编译单元数量并不是一个好主意。虽然这种方法似乎可以解决编译时间过长的问题,但它带来的附加效果有:
  1. 在开发过程中增加编译时间。通常开发人员一次只修改几个文件,对于三四个小文件的编译速度可能比一个非常大的文件要快。
  2. 正如你所提到的,代码的导航更加困难,我认为这点极其重要。
  3. 你可能会遇到一些干扰,因为多个.cpp文件被包含到一个.cxx文件中:

    a. 在cpp文件中定义本地宏new(用于调试构建),以检查内存泄漏是一种常见做法。不幸的是,在使用放置new的头文件之前(如某些STL和BOOST头文件),无法执行此操作

    b. 在cpp文件中添加using声明是一种常见做法。使用你的方法可能会导致后面包含的头文件出现问题

    c. 名称冲突更有可能发生

在我看来,更加清洁(但也许更昂贵)的加快编译时间的方法是使用一些分布式构建系统。它们对于清理构建特别有效。

2

我不确定这在你的情况下是否相关,但也许你可以使用声明而不是定义来减少你需要做的#include数量。此外,也许你可以使用pimpl技巧达到相同的目的。这将有望减少每次需要重新编译的源文件数量和必须引入的头文件数量。


1

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


这并不一定是正确的——OP提到他正在使用Visual Studio,而最新版本可以使用多个处理器来编译单个源文件,只需添加适当的命令行参数即可。 - Timo Geusch
不能保证加速的效果是一样的。知道如何使用多线程编译单个源文件是一个难题,我怀疑它是否像能够通过多个独立的编译来分布构建那样高效。 - Joseph Garvin

1

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接