如何加快g ++的编译时间(使用大量模板时)


70

这个问题也许有点奇怪,但是我怎样才能加快g ++的编译时间呢?我的C ++代码大量使用boost和模板。我已经尽可能地将其移出了头文件,并使用了-j选项,但是编译(和链接)仍需要花费相当长的时间。

有没有工具可以分析我的代码并指出编译器的瓶颈?还是可以以某种方式分析运行在我的代码上的编译器?这真的很好,因为有时我会觉得我花了太多时间盯着编译器控制台日志...



1
@Neil:太长了。尤其是在广泛使用的模板头文件发生变化的情况下,几乎每段代码都会重新编译。
Danvil

Answers:


54

对我来说最有用的是:

  • 在RAM文件系统上构建。在Linux上这是微不足道的。您可能还希望在RAM文件系统上保留公用头文件(预编译或实际的.h文件)的副本。
  • 预编译头文件。我每个(主要)库都有一个(例如Boost,Qt,stdlib)。
  • 尽可能声明而不是包含类。这样可以减少依赖性,从而减少更改头文件时需要重新编译的文件数。
  • 并行制作。这通常会根据具体情况提供帮助,但我在-j3全球范围内都有业务。但是,请确保您的依赖性文件在Makefile中是正确的,否则您可能会遇到问题。
  • 使用-O0,如果你不测试执行速度或代码大小(和你的电脑速度不够快,你没有太多在意(可能小)的性能损失)。
  • 每次保存时进行编译。有些人不喜欢这样,但是它可以让您及早发现错误,并且可以在后台完成操作,从而减少了编写和准备测试时需要等待的时间。

1
仅供参考,只是尝试制作带有一堆boost和stl库的预编译头文件。我把它放在ram fs上。没有加快。
kirill_igum 2014年

我认为这将我的编译(未完成)时间减少了大约十倍。
Evan Carslake '16

2
我怀疑在RAM文件系统中构建将不会改善任何事情。如果您有足够的RAM,Linux内核仅缓存写入和读取,因此从/向文件进行读取/写入应该已经使用RAM。
Hi-Angel

17

这是我在您所描述的非常相似的场景(加速,模板,gcc)下加快构建速度的工作

  • 建立在本地磁盘上,而不是像NFS这样的网络文件系统上
  • 升级到较新版本的gcc
  • 调查distcc
  • 更快的构建系统,尤其是更多的RAM

4
icecream / icecc优于distcc:“与distcc不同,Icecream使用中央服务器,该服务器动态地将编译作业调度到最快的免费服务器上。”
Zitrax

1
只有重新编译相同的源代码时,ccache才有用。开发时通常不是这种情况,因为Make应该已经意识到了这一点,因此不会再次启动编译器。ccache帮助在不同目录中查找相同文件,例如,Linux系统上的所有“配置” -Tests,或者是否在另一个目录中进行了清理或编译相同(或相似)源的操作。这通常发生在源Linux发行版(如Gentoo)中,在该发行版中,编译失败并重新启动后,将清除编译的工作目录。在这种情况下,ccache可以大大加快编译速度。
IanH

5
ccache当您使用DVCS或在团队中时,这将极大地帮助您,因为在两种情况下,即使文件是从另一个位置出现,也可能已经编译了文件。
Matthieu M.

17

我认为我们正在谈论分钟编译文件,即预编译头或本地磁盘的问题都不是问题。

带有深层模板代码(增强等)的长编译时间通常源于gcc在进行模板实例化时不友好的渐近行为,尤其是在使用模板默认参数模拟可变参数模板时。

这是一个文档,该文档以减少可变时间模板的名称命名缩短了编译时间:

cpptruths发表了一篇关于gcc-4.5如何更好地代表它以及如何使用可变参数模板出色地表现的文章:

IIRC然后BOOST有一种方法可以限制伪变量的模板默认参数的生成,我认为'g ++ -DBOOST_MPL_LIMIT_LIST_SIZE = 10'应该可以工作(默认值为20)

更新:这里还有一个不错的线程可以使用通用技术来加快在SO上的编译速度,这可能会很有用:

更新:这是关于编译模板时的性能问题,公认的答案也建议使用gcc-4.5,也提到clang作为一个积极的例子:


3
也可以看看 gcc.gnu.org/gcc-4.5/changes.htmlCompilation time for code that uses templates should now scale linearly with the number of instantiations rather than quadratically, as template instantiations are now looked up using hash tables.
塞巴斯蒂安·马赫

10

如果您要进行大量重新编译,则ccache可能会有所帮助。它实际上并不能加快编译速度,但是如果由于某种原因碰巧进行了无用的重新编译,它将为您提供缓存的结果。它可能给人以解决错误问题的印象,但是有时重建规则是如此复杂,以至于您实际上在新的构建过程中实际上要经过相同的编译步骤。

附加建议:如果您的代码使用clang编译,请改用它。通常比gcc快。


3

除了其他人添加的内容和您已经在做的事情(并行构建,编译器选项等)之外,考虑将模板隐藏在可通过接口访问的实现类中。那意味着不用像这样的类:

// ClsWithNoTemplates.h file, included everywhere

class ClsWithTemplates
{
    ComplicatedTemplate<abc> member;
    // ...

public:
    void FunctionUsingYourMember();
};

你应该有:

// ClsWithNoTemplates.h file:

class ClsWithTemplatesImplementation; // forward declaration
  // definition included in the ClsWithNoTemplates.cpp file
  // this class will have a ComplicatedTemplate<abc> member, but it is only 
  // included in your ClsWithNoTemplates definition file (that is only included once)


class ClsWithNoTemplates
{
     ClsWithTemplatesImplementation * impl; // no templates mentioned anywhere here
public:
    void FunctionUsingYourMember(); // call impl->FunctionUsingYourMember() internally
};

这会稍微改变您的OOP设计,但这是有好处的:现在包括“ ClsWithNoTemplates”的定义很快,而且您仅(预)编译“ ClsWithNoTemplates”的定义一次。

另外,如果更改实现代码,则可能不需要重新定义任何包含ClsWithNoTemplates.h的代码。

此更改将大大增加您的部分编译时间,并且在ClsWithNoTemplates是从库文件导出的公共接口的情况下也将有所帮助:由于仅更改实现时该文件不会更改,因此从属客户端代码不会更改。完全不需要重新编译。



2

如果有很多文件,则只包含一个包含所有其他.cpp文件的.cpp文件,就可以大大加快编译速度。当然,这要求您在使用宏时要格外小心,并已为每个文件定义了宏,因为它们现在对其他cpp文件可见。

如果文件很多,这可以大大减少编译时间。


也许。直到您的编译器吃完内存并开始交换。
KeithB

只是组合成合理的块,我并不是说您应该将300万行代码放在一个文件中。我在大型项目上使用了此技术,结果非常好,某些编译器的工作时间比原始时间少1/5。
Zitrax

1

实例化更少的模板和内联函数。尽可能多地进行预编译,然后直接链接它,而不是从头开始编译所有内容。确保您使用的是最新版本的GCC。

但是,这很简单,C ++是一种非常复杂的语言,并且编译它需要花费很多时间。


1

本文介绍了一种类似于“传统”非模板目标文件的编译模板代码的方法。节省编译和链接时间,每个模板实例化仅一行代码开销。


如果不想登录,我似乎无法下载本文的pdf。您是否需要实际付费才能访问本文?
greatwolf 2010年

0

通常,最昂贵的编译部分是(a)读取源文件(全部),以及(b)将编译器加载到每个源文件的内存中。

如果您有52个源(.cc)文件,每个文件包括#include 47个#include(.h)文件,则将加载52次编译器,并浏览2496个文件。根据文件中注释的密度,您可能会花费大量时间吃掉无用的字符。(在我看到的一个组织中,头文件的注释在66%到90%之间变化,只有10%-33%的文件是“有意义的”。提高这些文件的可读性的最佳方法是剥离删除每个最后的注释,仅保留代码。)

仔细研究程序的物理结构。查看是否可以合并源文件,并简化#include文件的层次结构。

几十年前,像IBM这样的公司已经了解了这一点,因此会编写他们的编译器,以便可以将一系列要编译的文件(不仅是一个文件)交给编译器,并且编译器只能加载一次。


2
“编译”注释需要多少时间?(这是由预处理程序完成的,与理解模板元编程相比,它应该不会很复杂吗?)
UncleBens 2010年

所以我g ++无法做到这一点?是否有一些工具可以告诉我,对于给定的源文件,编译器必须读取多少个包含文件?
Danvil 2010年

7
加载编译器不太可能发生多次;它几乎肯定会与第一个文件一起保留在第一个文件之后的磁盘缓存中。在过去的几十年中,计算机体系结构和速度发生了很大变化,而IBM鼎盛时期的常识可能在今天没有太大用处。
Mike Seymour 2010年

4
-1,代码注释有意义,删除它们可能很危险。如您
所言,

@Danvil,关键是编译器必须阅读注释,才能到达注释后的可执行行。即使预处理器已“读取”它,仍然必须对其进行读取,并且您仍然要支付读取它的费用。
约翰·斯特罗姆
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.