Dan*_*ein 518 c++ performance compilation
与C#和Java相比,编译C++文件需要很长时间.编译C++文件所需的时间比运行普通大小的Python脚本要长得多.我目前正在使用VC++,但它与任何编译器都是一样的.为什么是这样?
我能想到的两个原因是加载头文件和运行预处理器,但这似乎不应该解释为什么它需要这么长时间.
jal*_*alf 779
几个原因
每个编译单元都需要数百甚至数千个头文件(1)加载和(2)编译.通常必须为每个编译单元重新编译它们中的每一个,因为预处理器确保编译头的结果可能在每个编译单元之间变化.(可以在一个编译单元中定义宏,该编译单元改变标题的内容).
这可能是主要原因,因为它需要为每个编译单元编译大量代码,此外,每个头文件必须多次编译(每个编译单元包含它一次).
编译完成后,所有目标文件必须链接在一起.这基本上是一个单一的过程,不能很好地并行化,并且必须处理整个项目.
解析时语法极其复杂,在很大程度上取决于上下文,并且很难消除歧义.这需要很多时间.
在C#中,List<T>无论你在程序中有多少个List实例,它都是唯一被编译的类型.在C++中,vector<int>是一个完全独立的类型vector<float>,每个都必须单独编译.
除此之外,模板构成了编译器必须解释的完整图灵完整的"子语言",这可能变得非常复杂.即使是相对简单的模板元编程代码也可以定义递归模板,这些模板可以创建数十个模板实例.模板也可能导致极其复杂的类型,名称冗长,为链接器添加了大量额外的工作.(它必须比较许多符号名称,如果这些名称可以增长到数千个字符,那可能会变得相当昂贵).
当然,它们会加剧头文件的问题,因为模板通常必须在头文件中定义,这意味着必须为每个编译单元解析和编译更多的代码.在普通的C代码中,标头通常只包含前向声明,但实际代码很少.在C++中,几乎所有代码都驻留在头文件中并不罕见.
C++允许一些非常戏剧性的优化.C#或Java不允许完全删除类(它们必须用于反射目的),但即使是简单的C++模板元程序也可以轻松生成数十个或数百个类,所有这些类都在优化中被内联并消除相.
此外,编译器必须完全优化C++程序.AC#程序可以依赖JIT编译器在加载时执行额外的优化,C++没有得到任何这样的"第二次机会".编译器生成的内容是最优化的.
C++被编译为机器代码,这可能比字节码Java或.NET使用更复杂(特别是在x86的情况下).(这完全是出于完整性而被提及,因为它在评论等中提到过.在实践中,这一步骤不太可能占用总编译时间的一小部分).
大多数这些因素都由C代码共享,实际上编译效率相当高.解析步骤在C++中要复杂得多,并且可以占用更多的时间,但主要的攻击者可能是模板.它们很有用,并且使C++成为一种更强大的语言,但它们也会在编译速度方面付出代价.
tan*_*orm 37
任何编译器的减速都不一定相同.
我没有使用Delphi或Kylix但是在MS-DOS时代,Turbo Pascal程序几乎可以立即编译,而等效的Turbo C++程序只会抓取.
两个主要区别是一个非常强大的模块系统和允许单通道编译的语法.
编译速度当然不是C++编译器开发人员的优先考虑因素,但C/C++语法中也存在一些固有的复杂性,使得处理起来更加困难.(我不是C的专家,但Walter Bright是,并且在构建各种商业C/C++编译器之后,他创建了D语言.他的一个变化是强制执行无上下文语法以使语言更易于解析.)
此外,您会注意到通常设置Makefile,以便每个文件都在C中单独编译,因此如果10个源文件都使用相同的包含文件,则包含文件将被处理10次.
Jam*_*ran 36
解析和代码生成实际上相当快.真正的问题是打开和关闭文件.请记住,即使使用包含保护,编译器仍然打开.H文件,并读取每一行(然后忽略它).
一位朋友曾经(虽然在工作中感到无聊)拿走了他公司的应用程序并将所有内容 - 所有源文件和头文件 - 放入一个大文件中.编译时间从3小时降至7分钟.
Ala*_*lan 15
C++被编译成机器代码.所以你有预处理器,编译器,优化器,最后是汇编器,所有这些都必须运行.
Java和C#编译成字节码/ IL,Java虚拟机/ .NET Framework在执行之前执行(或JIT编译成机器代码).
Python是一种解释语言,也可以编译成字节码.
我确信还有其他原因,但一般来说,不必编译为本机机器语言可以节省时间.
Mar*_*ort 12
最大的问题是:
1)无限头重新分析.已经提到了.缓解(如#pragma一次)通常只对每个编译单元有效,而不是每个构建.
2)工具链通常被分成多个二进制文件(make,预处理器,编译器,汇编器,归档器,impdef,链接器和dlltool,在极端情况下),所有这些都必须为每次调用重新初始化并重新加载所有状态(编译器,汇编程序)或每两个文件(归档程序,链接程序和dlltool).
另见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系统也只是为了获取信息而调用某些程序.
小智 11
构建C/C++:真正发生了什么,为什么需要这么长时间
软件开发时间的相当大一部分不用于编写,运行,调试甚至设计代码,而是等待它完成编译.为了使事情变得快速,我们首先要了解编译C/C++软件时发生的事情.步骤大致如下:
现在,我们将更详细地研究每个步骤,重点关注如何更快地制作它们.
组态
这是开始构建的第一步.通常意味着运行配置脚本或CMake,Gyp,SCons或其他一些工具.对于非常大的基于Autotools的配置脚本,这可能需要一秒到几分钟的时间.
这个步骤相对很少发生.只需在更改配置或更改构建配置时运行它.如果没有更改构建系统,那么要做得更快,就没有太多工作要做.
构建工具启动
当您运行make或单击IDE上的构建图标(通常是make的别名)时会发生这种情况.构建工具二进制文件启动并读取其配置文件以及构建配置,这通常是相同的.
根据构建的复杂性和大小,这可能需要从几分之一秒到几秒钟.这本身就不会那么糟糕.不幸的是,大多数基于make的构建系统会导致make为每个构建调用数十到数百次.通常这是由递归使用make引起的(这很糟糕).
应该注意的是,Make的原因是如此缓慢并不是一个实现错误.Makefile的语法有一些怪癖,它们实现了一个非常快速的实现,但几乎不可能.与下一步结合使用时,此问题更加明显.
依赖性检查
一旦构建工具读取了其配置,就必须确定哪些文件已更改以及哪些文件需要重新编译.配置文件包含描述构建依赖关系的有向非循环图.此图通常在配置步骤中构建.构建工具启动时间和依赖扫描程序在每个构建上运行.它们的组合运行时确定了编辑 - 编译 - 调试周期的下限.对于小型项目,这个时间通常是几秒钟左右.这是可以忍受的.Make还有其他选择.其中最快的是Ninja,它是由Google工程师为Chromium构建的.如果您使用CMake或Gyp构建,只需切换到他们的Ninja后端.您无需在构建文件中自行更改任何内容,只需享受速度提升即可.但是,Ninja并没有打包在大多数发行版上,因此您可能必须自己安装它.
汇编
此时我们终于调用了编译器.切割一些角落,这是采取的近似步骤.
与流行的看法相反,编译C++实际上并不是那么慢.STL很慢,用于编译C++的大多数构建工具都很慢.但是,有更快的工具和方法来缓解语言的缓慢部分.
使用它们需要一点肘部油脂,但好处是不可否认的.更快的构建时间可以带来更快乐的开发人员,更高的灵活性以及最终更好的代码.
一些原因是:
1) C++ 语法比 C# 或 Java 更复杂,解析需要更多时间。
2)(更重要)C++ 编译器生成机器代码并在编译期间进行所有优化。C# 和 Java 只走了一半,将这些步骤留给 JIT。
编译语言总是需要比解释语言更大的初始开销.另外,也许你没有很好地构建你的C++代码.例如:
#include "BigClass.h"
class SmallClass
{
BigClass m_bigClass;
}
Run Code Online (Sandbox Code Playgroud)
编译速度比以下慢很多:
class BigClass;
class SmallClass
{
BigClass* m_bigClass;
}
Run Code Online (Sandbox Code Playgroud)
在较大的C++项目中减少编译时间的一种简单方法是制作一个*.cpp包含文件,其中包含项目中的所有cpp文件并进行编译.这会将标题爆炸问题减少一次.这样做的好处是编译错误仍将引用正确的文件.
例如,假设你有a.cpp,b.cpp和c.cpp ..创建一个文件:everything.cpp:
#include "a.cpp"
#include "b.cpp"
#include "c.cpp"
Run Code Online (Sandbox Code Playgroud)
然后通过make everything.cpp编译项目
大多数答案都有些不清楚,提到 C# 将始终运行得更慢,因为执行操作的成本在 C++ 中仅在编译时执行一次,此性能成本也因运行时依赖性而受到影响(需要加载更多的东西才能运行),更不用说 C# 程序总是有更高的内存占用,所有这些都导致性能与可用硬件的能力更密切相关。对于解释或依赖于 VM 的其他语言也是如此。
小智 5
我能想到的两个问题可能会影响 C++ 程序的编译速度。
可能的问题 #1 - 编译标头:(这可能已经或可能没有由另一个答案或评论解决。)Microsoft Visual C++(又名 VC++)支持预编译标头,我强烈推荐。当您创建一个新项目并选择您正在制作的程序类型时,屏幕上应该会出现一个设置向导窗口。如果您点击底部的“下一步>”按钮,该窗口将带您进入一个包含多个功能列表的页面;确保选中“预编译头”选项旁边的框。(注意:这是我在 C++ 中使用 Win32 控制台应用程序的经验,但对于 C++ 中的所有类型的程序,情况可能并非如此。)
可能的问题 #2 - 正在编译的位置:今年夏天,我参加了一个编程课程,我们不得不将所有项目存储在 8GB 闪存驱动器上,因为我们使用的实验室中的计算机每晚都在午夜被擦除,这会抹去我们所有的工作。如果您出于便携性/安全性等原因编译到外部存储设备,则可能需要很长时间时间(即使使用我上面提到的预编译头文件)让您的程序编译,特别是如果它是一个相当大的程序。在这种情况下,我对您的建议是在您使用的计算机的硬盘驱动器上创建和编译程序,并且无论何时您想/需要停止处理您的项目,无论出于何种原因,将它们转移到您的外部存储设备,然后单击“安全删除硬件并弹出媒体”图标,该图标应该显示为一个小闪存驱动器,位于一个带有白色复选标记的绿色小圆圈后面,以断开它。
我希望这可以帮助你; 如果有,请告诉我!:)
小智 5
简单地回答这个问题,C++ 是一种比市场上其他语言复杂得多的语言。它有一个遗留的包含模型,可以多次解析代码,并且它的模板库没有针对编译速度进行优化。
\n语法和 ADL
\n让我们通过一个非常简单的例子来看看 C++ 的语法复杂性:
\nx*y;\nRun Code Online (Sandbox Code Playgroud)\n虽然您\xe2\x80\x99d 可能会说上面是一个带有乘法的表达式,但在 C++ 中情况不一定如此。如果 x 是类型,则该语句实际上是指针声明。这意味着 C++ 语法是上下文相关的。
\n这里\xe2\x80\x99是另一个例子:
\nfoo<x> a;\nRun Code Online (Sandbox Code Playgroud)\n同样,您可能认为这是 foo 类型的变量“a”的声明,但它也可以解释为:
\n(foo < x) > a;\nRun Code Online (Sandbox Code Playgroud)\n这将使它成为一个比较表达式。
\nC++ 有一个称为参数相关查找 (ADL) 的功能。ADL 建立了控制编译器如何查找名称的规则。考虑以下示例:
\nnamespace A{\n struct Aa{}; \n void foo(Aa arg);\n}\nnamespace B{\n struct Bb{};\n void foo(A::Aa arg, Bb arg2);\n}\nnamespace C{ \n struct Cc{}; \n void foo(A::Aa arg, B::Bb arg2, C::Cc arg3);\n}\n\nfoo(A::Aa{}, B::Bb{}, C::Cc{});\nRun Code Online (Sandbox Code Playgroud)\nADL 规则规定,我们将考虑函数调用的所有参数来查找名称“foo”。在这种情况下,所有名为 \xe2\x80\x9cfoo\xe2\x80\x9d 的函数都将被考虑进行重载解析。此过程可能需要时间,尤其是在存在大量函数重载的情况下。在模板化环境中,ADL 规则变得更加复杂。
\n#包括
\n该命令可能会显着影响编译时间。根据包含的文件类型,预处理器可能仅复制几行代码,也可能复制数千行代码。
\n此外,该命令无法被编译器优化。如果头文件依赖于宏,您可以复制可以在包含之前修改的不同代码段。
\n这些问题有一些解决方案。您可以使用预编译头,它们是编译器对头中解析内容的内部表示。然而,如果没有用户的努力,这不能完成,因为预编译头假设头不依赖于宏。
\n模块功能为这个问题提供了语言级的解决方案。它\xe2\x80\x99s 从 C++20 版本开始可用。
\n模板
\n模板的编译速度具有挑战性。每个使用模板的翻译单元都需要包含它们,并且这些模板的定义需要可用。模板的某些实例化最终会成为其他模板的实例化。在某些极端情况下,模板实例化可能会消耗大量资源。使用模板并且不是为编译速度而设计的库可能会变得很麻烦,正如您可以在以下链接提供的元编程库的比较中看到的: http: //metaben.ch/。它们的编译速度差异很大。
\n如果您想了解为什么某些元编程库的编译时间比其他库更好,请观看有关 Chiel 规则的视频。
\n结论
\nC++是一种编译缓慢的语言,因为在该语言最初开发时,编译性能并不是最优先考虑的。结果,C++ 最终获得了在运行时可能有效但在编译时不一定有效的功能。
\nPS \xe2\x80\x93 我在 Incredibuild 工作,这是一家专门加速 C++ 编译的软件开发加速公司,欢迎大家免费试用。
\n