与c#和Java相比,编译c++文件需要很长时间。编译一个c++文件比运行一个正常大小的Python脚本花费的时间要长得多。我目前使用vc++,但它与任何编译器是一样的。为什么会这样?
我能想到的两个原因是加载头文件和运行预处理器,但这似乎不能解释为什么需要这么长时间。
与c#和Java相比,编译c++文件需要很长时间。编译一个c++文件比运行一个正常大小的Python脚本花费的时间要长得多。我目前使用vc++,但它与任何编译器是一样的。为什么会这样?
我能想到的两个原因是加载头文件和运行预处理器,但这似乎不能解释为什么需要这么长时间。
当前回答
编译语言总是比解释语言需要更大的初始开销。此外,也许您没有很好地组织您的c++代码。例如:
#include "BigClass.h"
class SmallClass
{
BigClass m_bigClass;
}
编译速度比:
class BigClass;
class SmallClass
{
BigClass* m_bigClass;
}
其他回答
构建C/ c++:到底发生了什么,为什么要花这么长时间
相当大一部分软件开发时间不是花在编写、运行、调试甚至设计代码上,而是花在等待代码完成编译上。 为了让事情变得更快,我们首先必须理解编译C/ c++软件时发生了什么。步骤大致如下:
配置 构建工具启动 依赖项检查 编译 链接
现在,我们将更详细地查看每个步骤,重点关注如何使它们更快。
配置
这是开始构建的第一步。通常意味着运行配置脚本或CMake、Gyp、SCons或其他工具。对于非常大的基于autotools的配置脚本,这可能需要一秒钟到几分钟的时间。
这一步很少发生。它只需要在更改配置或更改构建配置时运行。如果不改变构建系统,就没有多少事情可以加快这一步。
构建工具启动
这是在IDE上运行make或单击构建图标(通常是make的别名)时发生的情况。构建工具二进制文件启动并读取其配置文件以及构建配置,这通常是同一件事。
根据构建的复杂性和大小,这可能需要几秒到几秒的时间。这本身并没有那么糟糕。不幸的是,大多数基于make的构建系统在每次构建时都会调用几十到几百次make。这通常是由递归使用make(这是不好的)引起的。
应该注意的是,Make如此缓慢的原因并不是实现错误。Makefiles的语法有一些怪癖,使得真正快速的实现几乎不可能。当与下一步结合使用时,这个问题会更加明显。
依赖项检查
Once the build tool has read its configuration, it has to determine what files have changed and which ones need to be recompiled. The configuration files contain a directed acyclic graph describing the build dependencies. This graph is usually built during the configure step. Build tool startup time and the dependency scanner are run on every single build. Their combined runtime determines the lower bound on the edit-compile-debug cycle. For small projects this time is usually a few seconds or so. This is tolerable. There are alternatives to Make. The fastest of them is Ninja, which was built by Google engineers for Chromium. If you are using CMake or Gyp to build, just switch to their Ninja backends. You don’t have to change anything in the build files themselves, just enjoy the speed boost. Ninja is not packaged on most distributions, though, so you might have to install it yourself.
编译
此时,我们最终调用编译器。省事起见,以下是大致采取的步骤。
合并包括 解析代码 代码生成和优化
与流行的观点相反,编译c++实际上并没有那么慢。STL很慢,大多数用于编译c++的构建工具都很慢。然而,有更快的工具和方法来减轻语言中缓慢的部分。
使用它们需要一些体力,但好处是不可否认的。更快的构建时间会让开发人员更快乐,更敏捷,并最终产生更好的代码。
c++被编译成机器代码。所以你有预处理器,编译器,优化器,最后是汇编器,所有这些都必须运行。
Java和c#被编译成字节码/IL, Java虚拟机/。NET框架执行(或JIT编译成机器代码)之前执行。
Python是一种解释型语言,它也被编译成字节码。
我相信还有其他原因,但总的来说,不需要编译为本机机器语言可以节省时间。
几个原因
头文件
每个编译单元都需要(1)加载和(2)编译数百甚至数千个头文件。 每个编译单元通常都需要重新编译它们, 因为预处理器确保编译头文件的结果可能在每个编译单元之间有所不同。 (宏可以在一个编译单元中定义,它会改变头文件的内容)。
这可能是主要原因,因为它需要为每个编译单元编译大量的代码, 此外,每个头文件都必须编译多次 (对于包含它的每个编译单元一次)。
链接
一旦编译完成,所有的目标文件都必须链接在一起。 这基本上是一个整体过程,不能很好地并行化,并且必须处理您的整个项目。
解析
语法非常复杂,难以解析,严重依赖上下文,并且很难消除歧义。 这要花很多时间。
模板
在c#中,List<T>是唯一被编译的类型,无论程序中有多少个List实例化。 在c++中,vector<int>和vector<float>是一个完全独立的类型,它们必须分别编译。
Add to this that templates make up a full Turing-complete "sub-language" that the compiler has to interpret, and this can become ridiculously complicated. Even relatively simple template metaprogramming code can define recursive templates that create dozens and dozens of template instantiations. Templates may also result in extremely complex types, with ridiculously long names, adding a lot of extra work to the linker. (It has to compare a lot of symbol names, and if these names can grow into many thousand characters, that can become fairly expensive).
当然,它们加剧了头文件的问题,因为模板通常必须在头文件中定义, 这意味着必须为每个编译单元解析和编译更多的代码。 在纯C代码中,标头通常只包含前向声明,但实际代码很少。 在c++中,几乎所有的代码都驻留在头文件中是很常见的。
优化
c++允许进行一些非常戏剧化的优化。 c#或Java不允许完全消除类(为了反射的目的,它们必须存在), 但即使是一个简单的c++模板元程序也可以很容易地生成几十个或数百个类, 所有这些都在优化阶段被内联并再次消除。
此外,c++程序必须由编译器完全优化。 c#程序可以依赖JIT编译器在加载时执行额外的优化, c++没有任何这样的“第二次机会”。编译器生成的是它将要得到的优化。
机
c++被编译成机器代码,这可能比Java或. net使用的字节码更复杂(特别是在x86的情况下)。 (这只是出于完整性而提到的,因为它是在评论中提到的。 在实践中,这一步所花费的时间不太可能超过编译时间的一小部分)。
结论
这些因素中的大多数是由C代码共享的,这实际上是相当有效的编译。 在c++中,解析步骤要复杂得多,可能会占用更多的时间,但主要的问题可能是模板。 它们很有用,并使c++成为一种更强大的语言,但它们也在编译速度方面付出了代价。
一些原因是:
1) c++语法比c#或Java更复杂,需要更多的时间来解析。
2)(更重要的是)c++编译器生成机器代码,并在编译期间进行所有优化。c#和Java只走了一半,将这些步骤留给JIT。
最大的问题是:
1)无限头解析。已经提到过。缓解(如#pragma once)通常只适用于每个编译单元,而不是每个构建。
2)事实上,工具链经常被分离成多个二进制文件(make、预处理器、编译器、汇编器、归档器、impdef、链接器和dll工具),这些二进制文件必须在每次调用(编译器、汇编器)或每一对文件(归档器、链接器和dll工具)时重新初始化和重新加载所有状态。
请参见关于comp.compilers: http://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系统还调用一些程序只是为了获取信息时。