#将所有.cpp文件包含到一个编译单元中?


75

最近,我有理由要使用常规的Debug和Release配置来处理某些Visual Studio C ++项目,还需要处理“ Release All”和“ Debug All”,这是我以前从未见过的。

事实证明,项目的作者只有一个ALL.cpp,其中#includes所有其他.cpp文件。*所有配置仅构建此一个ALL.cpp文件。它当然不包含在常规配置中,并且常规配置不会构建ALL.cpp

我只是想知道这是否是常见的做法?它带来什么好处?(我的第一反应是闻起来很香。)

您可能会遇到什么样的陷阱?我能想到的是,如果您的.cpps中有匿名名称空间,它们不再是该cpp的“专用”对象,而是现在在其他cpps中也可见?

所有项目都构建DLL,因此将数据存储在匿名名称空间中不是一个好主意,对吗?但是功能会好吗?


2
绝对是病理性的;我只能猜测为什么有人会这样做(如果您可以直接问他们,您应该这样做)。通常,在C ++中,您想做相反的事情,不仅要保持实现文件的位置,而且要使头文件保持良好的分隔。(C ++项目的常见陷阱是“ #include spaghetti”,每个头文件都相互依赖。)也许要对编译器进行压力测试?
09年

1
一个简短的视频介绍了unitybuild的内置时间差异。
Özgür的

2
可以在cheind.wordpress.com上找到有关“ Unity Builds”的简介以及优点,缺点和完整的CMake集成。克里斯托夫
Chethoph)

我们的官方构建始终需要重建,因此我相信这种方法可以大大提高构建性能。但是由于官方版本主要由Devs占用,但是UnityBuild生成的pdbs可能对no-unitybuild代码无效。(我们不想使用统一的构建配置进行开发,对吧?)
Baye 2010年

将某些实现文件包含到另一个实现文件中的完全不同的原因是:这些文件可以自动生成。与处理现有代码中的更改注入相比,自动生成整个文件要容易得多。
谢尔盖·K。

Answers:


61

一些人(并且可以在Google上使用)将其称为“ Unity Build”。它疯狂地快速链接,并且编译也很快。它非常适合不需要迭代的构建,例如从中央服务器发布的构建,但是不一定适合增量构建。

这是要维护的PITA。

编辑:这是第一个google链接,以获取更多信息:http : //buffered.io/posts/the-magic-of-unity-builds/

快速实现的事情是,编译器只需要读入所有内容一次,然后进行编译,然后进行链接,而不是对每个.cpp文件执行此操作。

布鲁斯·道森(Bruce Dawson)在他的博客上对此写得更好:http : //randomascii.wordpress.com/2014/03/22/make-vc-compiles-fast-through-parallel-compilation/


27
这不仅仅是因为I / O。每个.cpp文件通常包含许多头文件。如果分别编译它们,那么您将多次编译头文件中的代码-每个.o文件一次。如果您使用“ Unity Build”,则这些头文件和其他所有文件仅被编译一次。
Frank

6
嗯...属于I / O。但是,是的,这更准确。
MSN

8
编译是不是通常的I / O约束,除非你有足够的内存。请在下面查看我的答案。磁盘缓存起作用。链接到的文章花了很多时间在诡辩上,以解释磁盘I / O的状况如何,但是由于磁盘高速缓存,它是无关紧要的。另外,多次编译头文件中的代码与I / O完全不同。冗余编译是问题所在,冗余I / O从未真正发生过。此处涵盖了一些构建统一的风险:randomascii.wordpress.com/2014/03/22/…–
Bruce Dawson

1
我想知道使用CMake时是否可以从馈入目标的源列表中生成统一构建.cpp文件。毕竟,CMake已经拥有了所有这些信息。我还想知道,当使用闪存驱动器而不是旋转驱动器时,所有这些变化如何,但是大多数人已经评论到原始I / O不是问题,而是重复编译。在非统一的构建中,我当然注意到闪存驱动器对原始I / O的改进。
合法化

3
还有一件事-不必使用一个统一的源文件来进行统一构建(例如,可以将100个文件目标构建为10个统一文件,每个文件都包含10个原始文件)。https://github.com/sakra/cotire使您可以控制使用了多少个统一源文件。另请查看https://github.com/onqtam/ucm-cotire的包装,以便更轻松地使用unity builds(以及更多)。
onqtam '16

57

Unity可以提高构建速度,主要有以下三个原因。第一个原因是,所有共享头文件只需要解析一次。许多C ++项目都有很多头文件,大多数或所有CPP文件都包含这些头文件,而这些文件的冗余解析是编译的主要成本,尤其是当您有许多短源文件时。预编译的头文件可以帮助您节省这笔费用,但是通常会有很多未预编译的头文件。

统一构建会提高构建速度的下一个主要原因是,编译器的调用次数更少。调用编译器会产生一些启动成本。

最后,减少冗余标头解析意味着减少内联函数的冗余代码生成,因此目标文件的总大小较小,这使链接速度更快。

Unity构建也可以提供更好的代码生成。

由于磁盘I / O减少,Unity构建不会更快。我已经介绍了许多使用xperf的版本,我知道我在说什么。如果您有足够的内存,则OS磁盘高速缓存将避免冗余I / O-头文件的后续读取将来自OS磁盘高速缓存。如果您没有足够的内存,那么统一编译甚至会通过导致编译器的内存占用超过可用内存并分页来使编译时间更糟。

磁盘I / O昂贵,这就是为什么所有操作系统都积极缓存数据以避免冗余磁盘I / O的原因。


1
最正确的答案在这里。对于大型项目,构建速度无可替代。BTW建议msbuild / LTCG将链接时间从4分钟增加到一整夜!(C语更好)。
ChocoBilly

请注意,VS 2015修复了以下错误,该错误使LTCG链接的运行速度显着提高-在最近的一些大型测试中,速度提高了三倍。connect.microsoft.com/VisualStudio/feedback/details/1064219/...
布鲁斯·道森

正确。C ++编译速度很慢,因为每个编译单元必须分别编译。为什么C ++编译需要这么长时间?
phuclv

7

我想知道ALL.cpp是否试图将整个项目放在单个编译单元中,以提高编译器针对大小优化程序的能力?

通常,某些优化仅在不同的编译单元内执行,例如删除重复的代码和内联。

话虽如此,我似乎还记得最近的编译器(Microsoft,Intel的编译器,但我不认为其中包括GCC)可以跨多个编译单元进行此优化,因此我怀疑此“技巧”是不必要的。

就是说,很奇怪是否确实存在任何差异。


2
视觉C ++可以对整个程序进行优化与/ LTCG开关(链接时代码生成)
罗杰Lipscombe

1
如今,GCC和clang也支持LTO
贾斯汀2012年

那肯定是不必要的。
Arafangion'2

这样可以大大降低SPEED和硬盘驱动器磁头的移动!
ПетърПетров

1
ПетърПетров:您是否有任何表明这一点的基准?它应该提高速度,因为这意味着不需要递归和重复地访问,打开和解析头文件。
Arafangion

2

我同意布鲁斯的观点。根据我的经验,我曾尝试为我的.dll项目之一实施Unity Build,该项目具有大量的头文件包含和许多.cpps;为了减少VS2010上的总体编译时间(已经用尽了“增量生成”选项),但我没有减少编译时间,而是用光了内存,并且该生成甚至无法完成编译。

但是要添加;我确实发现在Visual Studio中启用“多处理器编译”选项非常有助于减少编译时间。我不确定其他平台编译器是否提供这样的选项。


2
如果您同意并且不添加任何内容,那么似乎这不是答案。这听起来更像是一个故事和评论。
RacerNerd 2014年

1
其实; 我打算对自己在该问题上的经验(基于工业实力和规模较大的代码库)发表评论;对于尝试相同的人,因为Unity Builds的想法给人的印象是,它将几乎导致较少的编译;大型项目不是这种情况。我们首先要尝试的原因;但我无人能排除例外和某些设计注意事项。
spforay 2014年


0

我们有一个针对MacOS和Windows的多平台项目。我们有许多大型模板和许多标头。我们使用预编译标头将Visual Studio 2017 msvc的构建时间缩短了一半。对于MacOS,我们尝试了几天以减少构建时间,但是预编译的标头仅将构建时间减少到85%。突破是使用单个.cpp文件。我们只需使用脚本创建此ALL.cpp。我们的ALL.cpp包含项目所有.cpp文件的包含列表。我们使用XCode 9.0将构建时间减少到30%。唯一的缺点是我们必须为.cpp-Files中的所有私有未修饰的编译单元命名空间命名。否则,本地名称将发生冲突。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.