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

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

有没有什么工具可以分析我的代码并指出编译器的瓶颈?或者能够以某种方式分析在我的代码上运行的编译器?这将是非常好的,因为有时我有这样的印象,我花了太多的时间盯着编译器控制台日志..。

48924 次浏览

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

然而,一个简单的事实是,C + + 是一种极其复杂的语言,编译它需要相当长的时间。

下面是我在您所描述的非常类似的场景下为加速构建所做的工作(ost、 template、 gcc)

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

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

如果你有52个来源(。Cc)文件,每个 # 包含47 # include (。H)文件,你要加载编译器52次,你要犁通过2496个文件。根据文件中评论的密度,您可能会花费相当多的时间吃无用的字符。(在我见过的一个组织中,头文件有66% 到90% 的注释,只有10% 到33% 的文件是“有意义的”。要提高这些文件的可读性,最好的办法就是删除每一条注释,只留下代码。)

仔细看看你的程序是如何组织的。查看是否可以组合源文件,并简化 # include 文件的层次结构。

几十年前,像 IBM 这样的公司明白这一点,他们会编写自己的编译器,这样编译器就可以得到要编译的文件列表,而不仅仅是一个文件,而且编译器只能加载一次。

我假设我们讨论的是编译文件的 几分钟,也就是说,预编译头或本地磁盘问题不是问题。

使用深层模板代码(Boost 等)的长编译时间通常根植于模板实例化时 gcc 的不友好渐近行为,特别是当使用模板默认参数模拟可变模板时。

下面是一个文档,其中命名了减少编译时间作为可变模板的动机:

Cpptruth 发表了一篇文章,介绍了 gcc-4.5在这方面如何做得更好,以及它如何出色地使用其可变模板:

IIRC 有办法限制伪变量模板默认参数的生成,我认为‘ g + +-DBOOST _ MPL _ LIMIT _ LIST _ SIZE = 10’应该可以(默认值是20)

更新: 这里还有一个很好的线程,可以通过一些常规技术来加快 SO 的编译速度,它可能会很有用:

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

如果要进行大量的重新编译,Ccache可能会有所帮助。它实际上并没有加快编译速度,但是如果出于某种原因进行了无用的重新编译,它会给出一个缓存结果。它可能给人一种处理错误问题的印象,但是有时重新构建规则非常复杂,以至于在新构建期间实际上需要相同的编译步骤。

另外一个想法是: 如果你的代码是用 编译的,那就使用它,它通常比 gcc 快。

对我来说最有用的是:

  • 构建在 RAM 文件系统上。在 Linux 上这是微不足道的。您可能希望保留通用头文件的副本(预编译或实际的。H 文件)。
  • 每个(主要)库(例如 Boost、 Qt、 stdlib)都有一个预编译的标题
  • 尽可能声明而不是包含类。这减少了依赖性,从而减少了更改头文件时需要重新编译的文件数量。
  • 并行化 make 。这通常有助于个案的基础上,但我有 -j3的全球制作。但是,请确保 Makefile 中的依赖关系图是正确的,否则可能会出现问题。
  • 如果您没有测试执行速度或代码大小,请使用 -O0(而且您的计算机的速度已经足够快,以至于您不必太在意(可能很小的)性能损失)。
  • 每次保存时进行编译。有些人不喜欢这样,但是它允许您及早发现错误,并且可以在后台执行,从而减少您在完成编写并准备测试时所需等待的时间。

如果有很多文件,只要有一个就可以大大加快编译速度。Cpp 文件,其中 # 包括所有其他。Cpp 文件。这当然要求您对宏更加小心,因为您已经为每个文件定义了宏,因为它们现在对其他 cpp 文件是可见的。

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

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

// 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 设计,但是这是好的: 包括‘ ClsWithNoTemplate’的定义现在是 很快,你只需要(预先)编译一次‘ ClsWithNoTemplate’的定义。

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

这种改变会大大增加你的部分编译时间,并且在你的 ClsWithNoTemplate 是一个从库文件导出的公共接口的情况下也会有所帮助: 因为当你只改变实现时文件不会改变,所以你的依赖客户端代码根本不需要重新编译。

尝试 PIMPL 技术,这个问题: 可以使用哪些技术来加快 C + + 的编译速度?

它将防止编译器在您每次需要执行某些操作时遵循头文件和实现链。

本文 描述了一种类似于“传统的”非模板对象文件的编译模板代码的方法。节省编译和链接时间,每个模板实例化只有一行代码开销。