c++ - 加快编译速度 - 编译加速




为什么C++编译需要这么长时间? (10)

C ++被编译成机器码。 所以你有预处理器,编译器,优化器,最后是汇编器,所有这些都必须运行。

Java和C#被编译为字节码/ IL,并且Java虚拟机/ .NET Framework在执行之前执行(或JIT编译为机器码)。

Python是一种解释型语言,也被编译为字节码。

我确信还有其他原因,但一般来说,不需要编译为本地机器语言可以节省时间。

与C#和Java相比,编译C ++文件需要很长时间。 编译C ++文件比运行普通大小的Python脚本要花费更长的时间。 我目前正在使用VC ++,但对于任何编译器都是一样的。 为什么是这样?

我能想到的两个原因是加载头文件和运行预处理器,但这似乎并不能解释为什么它需要这么长时间。


构建C / C ++:究竟发生了什么,为什么需要这么长时间

软件开发时间的相当大部分不用于编写,运行,调试甚至设计代码,而是等待编译完成。 为了让事情变得更快,我们首先必须了解编译C / C ++软件时发生了什么。 步骤大致如下:

  • 组态
  • 构建工具启动
  • 依赖性检查
  • 汇编
  • 链接

现在我们将更详细地审视每一步,重点关注如何使它们变得更快。

组态

这是开始构建时的第一步。 通常意味着运行配置脚本或CMake,Gyp,SCons或其他工具。 对于非常大的基于Autotools的配置脚本,这可能需要一秒到几分钟的时间。

这一步发生得比较少。 只需在更改配置或更改构建配置时运行即可。 在改变构建系统的情况下,为了加快这一步骤,并没有太多的工作要做。

构建工具启动

当您运行make或单击IDE上的构建图标(通常是make的别名)时,会发生这种情况。 构建工具二进制文件启动并读取其配置文件以及构建配置,这通常是相同的事情。

根据构建的复杂性和大小,这可能需要几秒到几秒的时间。 本身并不会那么糟糕。 不幸的是,大多数基于make的构建系统在每个构建中都会导致调用数十到数百次。 通常这是由make的递归使用引起的(这是不好的)。

应该指出的是,Make非常慢的原因并不是一个执行错误。 Makefiles的语法有一些怪癖,使得一个非常快速的实现几乎不可能。 与下一步相结合时,这个问题更加明显。

依赖性检查

构建工具读取其配置后,必须确定哪些文件已更改以及哪些文件需要重新编译。 配置文件包含描述构建依赖关系的有向无环图。 此图通常在配置步骤中构建。 构建工具启动时间和依赖关系扫描程序在每个构建版本上运行。 它们的组合运行时间决定了编辑 - 编译 - 调试周期的下限。 对于小型项目来说,这个时间通常是几秒钟左右。 这是可以容忍的。 有替代品。 其中最快的是由Google工程师为Chromium制作的Ninja。 如果你正在使用CMake或Gyp来构建,只需切换到他们的忍者后端。 您不必在构建文件本身中更改任何内容,只需享受提速。 尽管忍者在大多数发行版上都没有打包,所以你可能需要自己安装它。

汇编

此时我们最终调用编译器。 切割一些角落,这里是采取的大致步骤。

  • 合并包括
  • 解析代码
  • 代码生成/优化

与流行的观点相反,编译C ++实际上并不是那么慢。 STL很慢,大多数用于编译C ++的构建工具都很慢。 但是,有更快的工具和方法可以缓解语言中较慢的部分。

使用它们需要一点肘部润滑脂,但好处是不可否认的。 更快的构建时间带来更快乐的开发人员,更敏捷,最终更好的代码。


任何编译器的放缓都不一定相同。

我没有使用Delphi或Kylix,但回到MS-DOS时代,Turbo Pascal程序几乎可以立即编译,而等效的Turbo C ++程序只会抓取。

两个主要区别是一个非常强大的模块系统和允许单次编译的语法。

对于C ++编译器开发人员来说,编译速度当然不是优先考虑的事情,但C / C ++语法中也存在一些固有的复杂性,这使得它更难处理。 (我不是C方面的专家,但Walter Bright是,并且在构建各种商业C / C ++编译器之后,他创建了D语言。 他的一个变化是强制使用上下文无关语法来使语言更易于解析。)

另外,您会注意到,通常会设置Makefiles,以便每个文件都在C中分别编译,因此如果10个源文件全部使用相同的包含文件,则包含文件将被处理10次。


你得到的折衷是程序运行速度更快。 在开发过程中,这可能会让你感到很冷淡,但一旦开发完成,这可能会很重要,并且该程序正在由用户运行。


另一个原因是使用C预处理器来定位声明。 即使使用了标头警卫,.h每次都被包括在内时,仍然需要一遍又一遍地进行解析。 一些编译器支持预编译头文件,可以帮助解决这个问题,但并不总是使用它们。

另请参阅: C ++常见问题解答


在较大的C ++项目中减少编译时间的简单方法是创建一个* .cpp包含文件,其中包含项目中的所有cpp文件并编译该文件。 这将头部爆炸问题减少到一次。 这样做的好处是编译错误仍然会引用正确的文件。

例如,假设你有a.cpp,b.cpp和c.cpp ..创建一个文件:everything.cpp:

#include "a.cpp"
#include "b.cpp"
#include "c.cpp"

然后通过制作everything.cpp来编译该项目


最大的问题是:

1)无限标题重新分析。 已经提到。 缓解(像#pragma一次)通常只对每个编译单元有效,而不是每个构建。

2)工具链经常被分成多个二进制文件(在极端情况下为make,预处理器,编译器,汇编器,归档器,impdef,链接器和dlltool),每次调用都必须重新初始化并重新加载所有状态编译器,汇编程序)或每一对文件(存档器,链接器和dlltool)。

另请参阅comp.compilers上的这个讨论: http://compilers.iecc.com/comparch/article/03-11-078 ://compilers.iecc.com/comparch/article/03-11-078特别是这一个:

http://compilers.iecc.com/comparch/article/02-07-128

请注意,comp.compilers的主持人John似乎同意这一点,并且这意味着如果一个工具链完全集成并实现预编译头文件,那么也应该可以为C实现类似的速度。 许多商业C编译器在一定程度上做到了这一点。

请注意,将所有东西都分解成单独二进制文件的Unix模型是Windows的一种最差情况模型(创建过程缓慢)。 比较Windows和* nix之间的GCC编译时间是非常明显的,特别是如果make / configure系统也调用一些程序来获取信息。


有两个我能想到的问题可能会影响C ++程序编译的速度。

可能的问题#1 - 编译头文件:(这可能已经或可能未被其他答案或评论解决。)Microsoft Visual C ++(AKA VC ++)支持预编译头文件,我强烈建议。 当您创建一个新项目并选择正在制作的程序类型时,屏幕上应该会出现设置向导窗口。 如果您点击底部的“下一步>”按钮,该窗口将带您进入具有多个功能列表的页面; 确保选中“预编译头”选项旁边的框。 (注意:这是我在C ++中使用Win32控制台应用程序的经验,但对于C ++中的各种程序可能不是这种情况。)

可能的问题#2 - 编译地点:今年夏天,我参加了一门编程课程,我们必须将所有项目存储在8GB闪存驱动器上,因为我们使用的实验室中的计算机在午夜时分每天晚上都被擦除,这会抹去我们所有的工作。 如果您为了便携性/安全性等目的而编译到外部存储设备,则需要很长时间 (即使使用上面提到的预编译头文件)来编译程序,特别是如果它相当大程序。 在这种情况下,我的建议是在您使用的计算机的硬盘驱动器上创建和编译程序,并且无论出于何种原因,只要您想/需要停止处理项目,就将它们转移到您的外部存储设备,然后单击“安全删除硬件并弹出介质”图标,该图标应该显示为一个带有白色复选标记的小绿色圆圈后面的小型闪存驱动器,以断开连接。

我希望这可以帮助你; 让我知道如果它! :)


编译语言总是需要比解释语言更大的初始开销。 另外,也许你没有很好地构建你的C ++代码。 例如:

#include "BigClass.h"

class SmallClass
{
   BigClass m_bigClass;
}

编译比以下更慢:

class BigClass;

class SmallClass
{
   BigClass* m_bigClass;
}

解析和代码生成实际上是相当快的。 真正的问题是打开和关闭文件。 请记住,即使使用包含警卫,编译器仍然打开.H文件,并读取每行(然后忽略它)。

一位朋友曾经(在工作中感到无聊)采取了公司的应用程序,并将所有源代码和头文件都放入一个大文件中。 编译时间从3小时减少到7分钟。







compilation