如何加快C++代码的编译速度 (转)

C++代码一直以其运行时的高性能高调面对世人, 可是提及编译速度,却只有低调的份了。好比我如今工做的源代码,哪怕使用Incredibuild调动近百台机子,一个完整的build也须要四个小时,恐怖!!!虽然平时开发通常不须要在本地作完整的build,但编译几个相关的工程就够你等上好一段时间的了(老外管这个叫monkey around,至关形象)。想一想若干年在一台单核2.8GHZ上工做时的场景 - 面前放本书,一点build按钮,就低头读一会书~~~往事不堪回首。html

能够想象,若是不加以重视,编译速度极有可能会成为开发过程当中的一个瓶颈。那么,为何C++它就编译的这么慢呢?apache

我想最重要的一个缘由应该是C++基本的"头文件-源文件"的编译模型:网络

  1. 每一个源文件做为一个编译单元,可能会包含上百甚至上千个头文件,而在每个编译单元,这些头文件都会被从硬盘读进来一遍,而后被解析一遍。
  2. 每一个编译单元都会产生一个obj文件,而后因此这些obj文件会被link到一块儿,而且这个过程很难并行。

这里,问题在于无数头文件的重复load与解析,以及密集的磁盘操做。框架

下面从各个角度给出一些加快编译速度的作法,主要仍是针对上面提出的这个关键问题。分布式

1、代码角度模块化

  • 在头文件中使用前置声明,而不是直接包含头文件。
    不要觉得你只是多加了一个头文件,因为头文件的"被包含"特性,这种效果可能会被无限放大。因此,要尽一切可能使头文件精简。不少时候前置申明某个namespace中的类会比较痛苦,而直接include会方便不少,千万要抵制住这种诱惑;类的成员,函数参数等也尽可能用引用,指针,为前置声明创造条件。
  • 使用Pimpl模式
    Pimpl全称为Private Implementation。传统的C++的类的接口与实现是混淆在一块儿的,而Pimpl这种作法使得类的接口与实现得以彻底分离。如此,只要类的公共接口保持不变,对类实现的修改始终只需编译该cpp;同时,该类提供给外界的头文件也会精简许多。
  • 高度模块化
    模块化就是低耦合,就是尽量的减小相互依赖。这里其实有两个层面的意思。一是文件与文件之间,一个头文件的变化,尽可能不要引发其余文件的从新编译;二是工程与工程之间,对一个工程的修改,尽可能不要引发太多其余工程的编译。这就要求头文件,或者工程的内容必定要单一,不要什么东西都往里面塞,从而引发没必要要的依赖。这也能够说是内聚性吧。

    以头文件为例,不要把两个不相关的类,或者没什么联系的宏定义放到一个头文件里。内容要尽可能单一,从而不会使包含他们的文件包含了不须要的内容。记得咱们曾经作过这么一个事,把代码中最"hot"的那些头文件找出来,而后分红多个独立的小文件,效果至关可观。svn

    其实咱们去年作过的refactoring,把众多DLL分离成UI与Core两个部分,也是有着相同的效果的 - 提升开发效率。函数

  • 删除冗余的头文件
    一些代码通过上十年的开发与维护,经手的人无数,颇有可能出现包含了没用的头文件,或重复包含的现象,去掉这些冗余的include是至关必要的。固然,这主要是针对cpp的,由于对于一个头文件,其中的某个include是否冗余很难界定,得看是否在最终的编译单元中用到了,而这样又可能出如今一个编译单元用到了,而在另一个编译单元中没用到的状况。
    以前曾写过一个Perl脚本用来自动去除这些冗余的头文件,在某个工程中居然去掉多达了5000多个的include。
  • 特别注意inline和template
    这是C++中两种比较"先进"的机制,可是它们却又强制咱们在头文件中包含实现,这对增长头文件的内容,从而减慢编译速度有着很大的贡献。使用以前,权衡一下。

2、综合技巧 性能

  • 预编译头文件(PCH)
    把一些经常使用但不常改动的头文件放在预编译头文件中。这样,至少在单个工程中你不须要在每一个编译单元里一遍又一遍的load与解析同一个头文件了。
  • Unity Build
    Unity Build作法很简单,把全部的cpp包含到一个cpp中(all.cpp) ,而后只编译all.cpp。这样咱们就只有一个编译单元,这意味着不须要重复load与解析同一个头文件了,同时由于只产生一个obj文件,在连接的时候也不须要那么密集的磁盘操做了,估计能有10x的提升,看看这个视频感觉一下其作法与速度吧。
  • ccache
    compiler cache, 经过cache上一次编译的结果,使rebuild在保持结果相同的状况下,极大的提升速度。咱们知道若是是build,系统会对比源代码与目标代码的时间来决定是否要从新编译某个文件,这个方法其实并不彻底可靠(好比从svn上拿了上个版本的代码),而ccache判断的原则则是文件的内容,相对来说要可靠的多。很惋惜的是,Visual Studio如今还不支持这个功能 - 其实彻底能够加一个新的命令,好比cache build,介于build与rebuild之间,这样,rebuild就能够基本不用了。
  • 不要有太多的Additional Include Directories
    编译器定位你include的头文件,是根据你提供的include directories进行搜索的。能够想象,若是你提供了100个包含目录,而某个头文件是在第100个目录下,定位它的过程是很是痛苦的。组织好你的包含目录,并尽可能保持简洁。

3、编译资源 ui

要提升速度,要么减小任务,要么加派人手,前面两个方面讲得都是减小任务,而事实上,在提升编译速度这块,加派人手仍是有着很是重要的做用的。

  • 并行编译
    买个4核的,或者8核的cpu,每次一build,就是8个文件并行着编,那速度,看着都爽。 要是大家老板不一样意,让他读读这篇文章:Hardware is Cheap, Programmers are Expensive
  • 更好的磁盘
    咱们知道,编译速度慢很大一部分缘由是磁盘操做,那么除了尽量的减小磁盘操做,咱们还能够作的就是加快磁盘速度。好比上面8个核一块工做的时候,磁盘极有可能成为最大的瓶颈。买个15000转的磁盘,或者SSD,或者RAID0的,总之,越快越好。
  • 分布式编译
    一台机子的性能始终是有限的,利用网络中空闲的cpu资源,以及专门用来编译的build server来帮助你编译才能从根本上解决咱们编译速度的问题,想一想原来要build 1个多小时工程的在2分钟内就能搞定,你就知道你必定不能没有它 - Incredibuild
  • 并行,其实还能够这么作。
    这是一个比较极端的状况,若是你用了Incredibuild,对最终的编译速度仍是不满意,怎么办?其实只要跳出思惟的框架,编译速度仍是能够有质的飞跃的 - 前提是你有足够多的机器:

    假设你有solution A和solution B,B依赖于A,因此必须在A以后Build B。其中A,B Build各须要1个小时,那么总共要2个小时。但是B必定要在A以后build吗?跳出这个思惟框架,你就有了下述方案:

    • 同时开始build A和B 。
    • A的build成功,这里虽然B的build失败了,但都只是失败在最后的link上。
    • 从新link B中的project。

    这样,经过让A的build与B的编译并行,最后link一下B中的project,整个编译速度应该可以控制在1个小时15分钟以内。

另外,这本书谈了不少这方面的内容:大规模C++程序设计

相关文章
相关标签/搜索