如何减少单个.cpp文件的大型C ++库的编译时间?

时间:2015-07-30 20:36:08

标签: c++ include-guards pre-compilation compilation-time dependency-graph

我们正在开发一个C++ library,目前有超过500个单独的.cpp文件。这些都被编译并存档到静态库中。即使使用并行构建,这也需要几分钟时间。我想减少这个编译时间。

每个文件平均有110行,其中有一个函数或两个函数。但是,对于每个.cpp文件,都有一个相应的.h标头,许多.cpp文件经常包含这些标头。例如,A.hA.cppB.cpp可能包含C.cpp,等等。

我首先想要 profile 编译过程。有没有办法找出花多少时间做什么?我担心浪费很多时间只是打开头文件来检查包含警卫并忽略该文件。

如果这种事情是罪魁祸首,减少编译时间的最佳做法是什么?

我愿意添加新的分组标题,但可能不愿意更改这种多文件布局,因为这样我们的库也可以作为所需的标头库。< / p>

4 个答案:

答案 0 :(得分:2)

  

如果这种事情是罪魁祸首,减少编译时间的最佳做法是什么?

如果您的预处理器支持#pragma once指令,请使用它。这将确保.h文件不会被多次读取。

如果没有,请在.cpp文件中使用#include警卫。

说你有

A.H:

#ifndef A_H
#define A_H

...

#endif

您可以在A.cpp中使用以下方法:

#ifndef A_H
#include "A.h"
#endif

您需要为每个.h文件重复该模式。 E.g。

#ifndef B_H
#include "B.h"
#endif

#ifndef C_H
#include "C.h"
#endif

您可以在What is the function of include guard in .cpp (not in .h)?的.cpp文件中详细了解#include警卫的使用情况。

答案 1 :(得分:2)

我不知道你是否已经这样做了,但使用前向声明而不是标题中的include应该会提高编译速度。有关详细信息,请参阅此问题:

Should one use forward declarations instead of includes wherever possible?

减少编译时间的另一种方法是使用ccache。它缓存了以前编译的结果。

https://ccache.samba.org

答案 2 :(得分:2)

真的很难说。

我致力于改善我们工作项目的编译时间,发现ONE文件花了15分钟(在-O2编译时,在-O0编译约15秒)并编译两次,因此,总编译时间约为60-70分钟,这大约是一半时间。关闭一个优化功能使一个文件减少到大约20秒而不是15分钟...这个文件生成一个机器生成的函数和几万行长,这导致编译器做一些魔术很长的东西(大概是一些O(N ^ 2)算法)。

如果你有一个小函数然后依次调用许多小函数,最终通过内联层变成一个大文件,也会发生这种情况。

在其他时候,我发现减少文件数量并将更多代码放在一个文件中效果更好。

一般来说,我的经验(包括我自己的编译器项目,以及其他人/公司的编译器)都是因为它不是解析和读取花费时间的文件,但各种优化和代码生成通过。您可以通过使用-fsyntax-only或编译器调用所有文件来编译所有文件。那将只读取源并检查它在语法上是否正确。如果您还没有,请尝试使用-O0进行编译。通常,特定的优化过程是问题,有些过程比其他过程差,因此检查特定-O选项中的个别优化通过是否有用 - 在gcc中可以列出{ {1}} [在这种情况下为-Q -O2 --help=optimizers]。

你真的需要弄清楚编译器花费的时间。如果问题在于您花费大部分时间来优化代码,那么改变代码就没有意义。如果花费在解析上花费时间并且优化不会增加额外的时间,那么削减优化器就没有意义。如果没有真正建立你的项目,肯定很难说。

另一个提示是检查-O2以查看您的编译进程是否每次使用100%cpu - 如果没有,您可能在编译器中没有足够的内存。我的工作项目有一个构建选项,它可以杀死&#34;我的台式机通过运行这么多的内存,整个系统只是停止 - 即使在网络浏览器中从一个选项卡切换到另一个选项卡需要15-30秒。唯一的解决方案是少运行top [当然,我通常会忘记,并且在那时 - 所以如果我不想打断它,我会去吃午饭,长时间喝咖啡休息时间或者其他一些直到它完成,因为机器是不可用的]。这仅适用于调试版本,因为将大型代码库的调试信息放在一起会占用大量内存[显然!]

答案 3 :(得分:0)

将代码构造为使用 PIMPL 范例。这两个主要好处是:

  • 您可以向用户隐藏所有实现(成员vars等)
  • 如果您更改实现文件,则“通常”仅此区域需要重新编译,而不是完全重建。

有关概述,请参见here