简体   繁体   English

Go 怎么编译得这么快?

[英]How does Go compile so quickly?

I've Googled and poked around the Go website, but I can't seem to find an explanation for Go's extraordinary build times.我在 Google 上搜索并浏览了 Go 网站,但似乎找不到对 Go 非凡构建时间的解释。 Are they products of the language features (or lack thereof), a highly optimized compiler, or something else?它们是语言特性(或缺乏)的产物、高度优化的编译器还是其他什么? I'm not trying to promote Go;我不是要推广围棋; I'm just curious.我只是好奇。

Dependency analysis.依赖分析。

The Go FAQ used to contain the following sentence: Go FAQ曾经包含以下句子:

Go provides a model for software construction that makes dependency analysis easy and avoids much of the overhead of C-style include files and libraries. Go 提供了一个软件构建模型,它使依赖分析变得容易,并避免了 C 风格的包含文件和库的大部分开销。

While the phrase is not in the FAQ anymore, this topic is elaborated upon in the talk Go at Google , which compares the dependency analysis approach of C/C++ and Go.虽然这个短语不再出现在 FAQ 中,但这个主题在 Google 的 Go 演讲中进行了详细说明,该演讲比较了 C/C++ 和 Go 的依赖分析方法。

That is the main reason of fast compilation.这是快速编译的主要原因。 And this is by design.这是设计使然。

I think it's not that Go compilers are fast , it's that other compilers are slow .我认为不是 Go 编译器,而是其他编译器

C and C++ compilers have to parse enormous amounts of headers - for example, compiling C++ "hello world" requires compiling 18k lines of code, which is almost half a megabyte of sources! C 和 C++ 编译器必须解析大量的头文件——例如,编译 C++“hello world”需要编译 18k 行代码,这几乎是半兆字节的源代码!

$ cpp hello.cpp | wc
  18364   40513  433334

Java and C# compilers run in a VM, which means that before they can compile anything, the operating system has to load the whole VM, then they have to be JIT-compiled from bytecode to native code, all of which takes some time. Java 和 C# 编译器在 VM 中运行,这意味着在编译任何东西之前,操作系统必须加载整个 VM,然后必须将它们从字节码 JIT 编译为本机代码,所有这些都需要一些时间。

Speed of compilation depends on several factors.编译速度取决于几个因素。

Some languages are designed to be compiled fast.某些语言旨在快速编译。 For example, Pascal was designed to be compiled using a single-pass compiler.例如,Pascal 被设计为使用单程编译器进行编译。

Compilers itself can be optimized too.编译器本身也可以优化。 For example, the Turbo Pascal compiler was written in hand-optimized assembler, which, combined with the language design, resulted in a really fast compiler working on 286-class hardware.例如,Turbo Pascal 编译器是用手工优化的汇编器编写的,结合语言设计,产生了一个在 286 级硬件上工作的非常快速的编译器。 I think that even now, modern Pascal compilers (eg FreePascal) are faster than Go compilers.我认为即使是现在,现代 Pascal 编译器(例如 FreePascal)也比 Go 编译器快。

There are multiple reasons why the Go compiler is much faster than most C/C++ compilers: Go 编译器比大多数 C/C++ 编译器快得多的原因有很多:

  • Top reason : Most C/C++ compilers exhibit exceptionally bad designs (from compilation speed perspective).首要原因:大多数 C/C++ 编译器都表现出非常糟糕的设计(从编译速度的角度来看)。 Also, from compilation speed perspective, some parts of the C/C++ ecosystem (such as editors in which programmers are writing their codes) aren't designed with speed-of-compilation in mind.此外,从编译速度的角度来看,C/C++ 生态系统的某些部分(例如程序员在其中编写代码的编辑器)在设计时并未考虑到编译速度。

  • Top reason : Fast compilation speed was a conscious choice in the Go compiler and also in the Go language首要原因:在 Go 编译器和 Go 语言中,快速编译速度是一个有意识的选择

  • The Go compiler has a simpler optimizer than C/C++ compilers Go 编译器的优化器比 C/C++ 编译器更简单

  • Unlike C++, Go has no templates and no inline functions.与 C++ 不同,Go 没有模板和内联函数。 This means that Go doesn't need to perform any template or function instantiation.这意味着 Go 不需要执行任何模板或函数实例化。

  • The Go compiler generates low-level assembly code sooner and the optimizer works on the assembly code, while in a typical C/C++ compiler the optimization passes work on an internal representation of the original source code. Go 编译器更快地生成低级汇编代码,优化器处理汇编代码,而在典型的 C/C++ 编译器中,优化传递处理原始源代码的内部表示。 The extra overhead in the C/C++ compiler comes from the fact that the internal representation needs to be generated. C/C++ 编译器中的额外开销来自需要生成内部表示的事实。

  • Final linking (5l/6l/8l) of a Go program can be slower than linking a C/C++ program, because the Go compiler is going through all of the used assembly code and maybe it is also doing other extra actions that C/C++ linkers aren't doing Go 程序的最终链接 (5l/6l/8l) 可能比链接 C/C++ 程序慢,因为 Go 编译器会检查所有使用过的汇编代码,而且它可能还会执行 C/C++ 之外的其他额外操作链接器没有做

  • Some C/C++ compilers (GCC) generate instructions in text form (to be passed to the assembler), while the Go compiler generates instructions in binary form.一些 C/C++ 编译器 (GCC) 生成文本形式的指令(传递给汇编器),而 Go 编译器生成二进制形式的指令。 Extra work (but not much) needs to be done in order to transform the text into binary.为了将文本转换为二进制,需要做额外的工作(但不多)。

  • The Go compiler targets only a small number of CPU architectures, while the GCC compiler targets a large number of CPUs Go 编译器仅针对少量 CPU 架构,而 GCC 编译器针对大量 CPU

  • Compilers which were designed with the goal of high compilation speed, such as Jikes, are fast.以高编译速度为目标而设计的编译器,例如 Jikes,速度很快。 On a 2GHz CPU, Jikes can compile 20000+ lines of Java code per second (and the incremental mode of compilation is even more efficient).在 2GHz 的 CPU 上,Jikes 每秒可以编译 20000+ 行 Java 代码(增量编译模式更高效)。

Compilation efficiency was a major design goal:编译效率是一个主要的设计目标:

Finally, it is intended to be fast: it should take at most a few seconds to build a large executable on a single computer.最后,它旨在快速:在单台计算机上构建大型可执行文件最多只需要几秒钟。 To meet these goals required addressing a number of linguistic issues: an expressive but lightweight type system;为了实现这些目标,需要解决许多语言问题:一个富有表现力但轻量级的类型系统; concurrency and garbage collection;并发和垃圾收集; rigid dependency specification;严格的依赖规范; and so on.等等。 FAQ常问问题

The language FAQ is pretty interesting in regards to specific language features relating to parsing:关于与解析相关的特定语言功能,语言常见问题非常有趣:

Second, the language has been designed to be easy to analyze and can be parsed without a symbol table.其次,该语言的设计易于分析,无需符号表即可解析。

While most of the above is true, there is one very important point that was not really mentionend: Dependency management.虽然以上大部分内容都是正确的,但有一个非常重要的点并未真正提及:依赖管理。

Go only needs to include the packages that you are importing directly (as those already imported what they need). Go 只需要包含您直接导入的包(因为那些已经导入了他们需要的包)。 This is in stark contrast to C/C++, where every single file starts including x headers, which include y headers etc. Bottom line: Go's compiling takes linear time wrt to the number of imported packages, where C/C++ take exponential time.这与 C/C++ 形成鲜明对比,在 C/C++ 中,每个文件都以 x 头开始,其中包括 y 头等。 底线:Go 的编译时间与导入包的数量成线性关系,而 C/C++ 需要指数时间。

A good test for the translation efficiency of a compiler is self-compilation: how long does it take a given compiler to compile itself?编译器翻译效率的一个很好的测试是自编译:给定的编译器编译自己需要多长时间? For C++ it takes a very long time (hours?).对于 C++,它需要很长时间(几小时?)。 By comparison, a Pascal/Modula-2/Oberon compiler would compile itself in less than one second on a modern machine [1].通过比较,一个Pascal / Modula-2的/奥伯伦编译器将编译本身在不到秒钟的现代化机器[1]上。

Go has been inspired by these languages, but some of the main reasons for this efficiency include: Go 受到了这些语言的启发,但这种效率的一些主要原因包括:

  1. A clearly defined syntax that is mathematically sound, for efficient scanning and parsing.一个明确定义的语法,在数学上是合理的,用于高效扫描和解析。

  2. A type-safe and statically-compiled language that uses separate compilation with dependency and type checking across module boundaries, to avoid unnecessary re-reading of header files and re-compiling of other modules - as opposed to independent compilation like in C/C++ where no such cross-module checks are performed by the compiler (hence the need to re-read all those header files over and over again, even for a simple one-line "hello world" program).的类型安全和静态编译语言,它使用依赖和类型跨越模块边界校验,以避免分离编译不必要的重新读取的头文件和其它模块的重新编译-在C / C相对独立编译像++其中编译器不会执行此类跨模块检查(因此需要一遍又一遍地重新读取所有这些头文件,即使对于简单的一行“hello world”程序也是如此)。

  3. An efficient compiler implementation (eg single-pass, recursive-descent top-down parsing) - which of course is greatly helped by points 1 and 2 above.一个高效的编译器实现(例如单程、递归下降自顶向下解析)——当然上面的第 1 点和第 2 点对这点有很大帮助。

These principles have already been known and fully implemented in the 1970s and 1980s in languages like Mesa, Ada, Modula-2/Oberon and several others, and are only now (in the 2010s) finding their way into modern languages like Go (Google), Swift (Apple), C# (Microsoft) and several others.这些原则在 1970 年代和 1980 年代已经在 Mesa、Ada、Modula-2/Oberon 和其他几种语言中为人所知并完全实现,并且直到现在(在 2010 年代)才进入 Go(Google)等现代语言、Swift(苹果)、C#(微软)和其他几个。

Let's hope that this will soon be the norm and not the exception.让我们希望这将很快成为常态而不是例外。 To get there, two things need to happen:要到达那里,需要做两件事:

  1. First, software platform providers such as Google, Microsoft and Apple should start by encouraging application developers to use the new compilation methodology, while enabling them to re-use their existing code base.首先,谷歌、微软和苹果等软件平台提供商应首先鼓励应用程序开发人员使用新的编译方法,同时使他们能够重用现有的代码库。 This is what Apple is now trying to do with the Swift programming language, which can co-exist with Objective-C (since it uses the same runtime environment).这就是 Apple 现在试图用 Swift 编程语言做的事情,它可以与 Objective-C 共存(因为它使用相同的运行时环境)。

  2. Second, the underlying software platforms themselves should eventually be re-written over time using these principles, while simultaneously redesigning the module hierarchy in the process to make them less monolithic.其次,随着时间的推移,底层软件平台本身最终应该使用这些原则重新编写,同时在过程中重新设计模块层次结构,使它们不那么单一。 This is of course a mammoth task and may well take the better part of a decade (if they are courageous enough to actually do it - which I am not at all sure in the case of Google).这当然是一项艰巨的任务,很可能需要十年中的大部分时间(如果他们有足够的勇气实际去做的话——对于谷歌,我完全不确定)。

In any case, it's the platform that drives language adoption, and not the other way around.无论如何,它是推动语言采用的平台,而不是相反。

References:参考:

[1] http://www.inf.ethz.ch/personal/wirth/ProjectOberon/PO.System.pdf , page 6: "The compiler compiles itself in about 3 seconds". [1] http://www.inf.ethz.ch/personal/wirth/ProjectOberon/PO.System.pdf ,第 6 页:“编译器在大约 3 秒内自行编译”。 This quote is for a low cost Xilinx Spartan-3 FPGA development board running at a clock frequency of 25 MHz and featuring 1 MByte of main memory.此报价适用于以 25 MHz 时钟频率运行并具有 1 MB 主存储器的低成本 Xilinx Spartan-3 FPGA 开发板。 From this one can easily extrapolate to "less than 1 second" for a modern processor running at a clock frequency well above 1 GHz and several GBytes of main memory (ie several orders of magnitude more powerful than the Xilinx Spartan-3 FPGA board), even when taking I/O speeds into account.对于以远高于 1 GHz 的时钟频率和几 GB 的主存储器(即比 Xilinx Spartan-3 FPGA 板强大几个数量级)运行的现代处理器,从这一点可以轻松推断出“不到 1 秒”,即使考虑到 I/O 速度。 Already back in 1990 when Oberon was run on a 25MHz NS32X32 processor with 2-4 MBytes of main memory, the compiler compiled itself in just a few seconds.早在 1990 年,当 Oberon 运行在具有 2-4 MB 主存储器的 25MHz NS32X32 处理器上时,编译器只需几秒钟即可自行编译。 The notion of actually waiting for the compiler to finish a compilation cycle was completely unknown to Oberon programmers even back then.即使在当时,Oberon 程序员也完全不知道实际等待编译器完成编译周期的概念。 For typical programs, it always took more time to remove the finger from the mouse button that triggered the compile command than to wait for the compiler to complete the compilation just triggered.对于典型的程序,从触发编译命令的鼠标按钮上移开手指总是比等待编译器完成刚刚触发的编译花费更多的时间。 It was truly instant gratification, with near-zero wait times.这是真正的即时满足,等待时间几乎为零。 And the quality of the produced code, even though not always completely on par with the best compilers available back then, was remarkably good for most tasks and quite acceptable in general.生成的代码的质量,尽管并不总是完全与当时可用的最好的编译器相提并论,但对于大多数任务来说都非常好,而且总体上是可以接受的。

Go was designed to be fast, and it shows. Go 被设计得很快,它显示出来。

  1. Dependency Management: no header file, you just need to look at the packages that are directly imported (no need to worry about what they import) thus you have linear dependencies.依赖管理:没有头文件,您只需要查看直接导入的包(无需担心它们导入的内容),因此您具有线性依赖关系。
  2. Grammar: the grammar of the language is simple, thus easily parsed.语法:语言的语法很简单,因此很容易解析。 Although the number of features is reduced, thus the compiler code itself is tight (few paths).尽管特征数量减少了,因此编译器代码本身很紧凑(路径很少)。
  3. No overload allowed: you see a symbol, you know which method it refers to.不允许重载:你看到一个符号,你知道它指的是哪个方法。
  4. It's trivially possible to compile Go in parallel because each package can be compiled independently.并行编译 Go 是非常可能的,因为每个包都可以独立编译。

Note that Go isn't the only language with such features (modules are the norm in modern languages), but they did it well.请注意,Go 并不是唯一具有此类功能的语言(模块是现代语言的规范),但它们做得很好。

Quoting from the book " The Go Programming Language " by Alan Donovan and Brian Kernighan:引用 Alan Donovan 和 Brian Kernighan 所著的“ The Go Programming Language ”一书:

Go compilation is notably faster than most other compiled languages, even when building from scratch. Go 编译明显比大多数其他编译语言快,即使是从头开始构建也是如此。 There are three main reasons for the compiler's speed.编译器的速度有三个主要原因。 First, all imports must be explicitly listed at the beginning of each source file, so the compiler does not have to read and process an entire file to determine its dependencies.首先,必须在每个源文件的开头明确列出所有导入,因此编译器不必读取和处理整个文件来确定其依赖关系。 Second, the dependencies of a package form a directed acyclic graph, and because there are no cycles, packages can be compiled separately and perhaps in parallel.其次,一个包的依赖关系形成了一个有向无环图,因为没有循环,包可以单独编译,也可以并行编译。 Finally, the object file for a compiled Go package records export information not just for the package itself, but for its dependencies too.最后,编译后的 Go 包的目标文件不仅记录包本身的导出信息,还记录其依赖项的导出信息。 When compiling a package, the compiler must read one object file for each import but need not look beyond these files.编译包时,编译器必须为每次导入读取一个目标文件,但不必查看这些文件之外的内容。

The basic idea of compilation is actually very simple.编译的基本思想其实很简单。 A recursive-descent parser, in principle, can run at I/O bound speed.原则上,递归下降解析器可以以 I/O 限制速度运行。 Code generation is basically a very simple process.代码生成基本上是一个非常简单的过程。 A symbol table and basic type system is not something that requires a lot of computation.符号表和基本类型系统不需要大量计算。

However, it is not hard to slow down a compiler.然而,降低编译器的速度并不难。

If there is a preprocessor phase, with multi-level include directives, macro definitions, and conditional compilation, as useful as those things are, it is not hard to load it down.如果有一个预处理器阶段,具有多级包含指令、宏定义和条件编译,尽管这些东西很有用,加载它并不难。 (For one example, I'm thinking of the Windows and MFC header files.) That is why precompiled headers are necessary. (例如,我想到的是 Windows 和 MFC 头文件。)这就是为什么需要预编译头文件的原因。

In terms of optimizing the generated code, there is no limit to how much processing can be added to that phase.在优化生成的代码方面,可以向该阶段添加多少处理没有限制。

Simply ( in my own words ), because the syntax is very easy ( to analyze and to parse )简单(用我自己的话),因为语法非常简单(分析和解析)

For instance, no type inheritance means, not problematic analysis to find out if the new type follows the rules imposed by the base type.例如,没有类型继承意味着,没有问题分析来找出新类型是否遵循基类型强加的规则。

For instance in this code example: "interfaces" the compiler doesn't go and check if the intended type implement the given interface while analyzing that type.例如在这个代码示例中: “interfaces”编译器在分析该类型时不会去检查预期的类型是否实现了给定的接口。 Only until it's used ( and IF it is used ) the check is performed.只有在它被使用之前(如果它被使用)才会执行检查。

Other example, the compiler tells you if you're declaring a variable and not using it ( or if you are supposed to hold a return value and you're not )另一个例子,编译器会告诉你是否声明了一个变量而不使用它(或者如果你应该持有一个返回值而你没有)

The following doesn't compile:以下不编译:

package main
func main() {
    var a int 
    a = 0
}
notused.go:3: a declared and not used

This kinds of enforcements and principles make the resulting code safer, and the compiler doesn't have to perform extra validations that the programmer can do.这种强制措施和原则使生成的代码更安全,编译器不必执行程序员可以执行的额外验证。

At large all these details make a language easier to parse which result in fast compilations.总的来说,所有这些细节使语言更容易解析,从而实现快速编译。

Again, in my own words.再次,用我自己的话来说。

i think Go was designed in parallel with compiler creation, so they were best friends from birth. 我认为Go是与编译器创建并行设计的,因此他们从诞生起就是最好的朋友。 (IMO) (IMO)

  • Go imports dependencies once for all files, so the import time doesn't increase exponentially with project size. Go 为所有文件导入一次依赖项,因此导入时间不会随着项目大小呈指数增长。
  • Simpler linguistics means interpreting them takes less computing.更简单的语言学意味着解释它们需要更少的计算。

What else?还有什么?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 Google的Play商店应用如何快速启动? - How does Google's Play Store app start so quickly? iPhone照片的应用程序如何快速流畅地加载这么多图像? - How does the iPhone photo's app load so many images so quickly and smoothly? 优化代码,使其快速运行 - optimize code so that it run quickly 我该怎么办-像2的++幂一样快速获得2的幂? - How can I do — mod power of two quickly just like one does ++ power of two quickly? Mongodb Architecture-如何设计,以便我可以快速知道可用的产品 - Mongodb Architecture - How to design so I can quickly know what is available Clearcase签入的速度如此之慢:签入大量已修改的文件需要多快? - Clearcase is so slow on checkin: how quickly checkin a large set of modified files? sql查询的优化程度是多少? - How far does optimization in sql queries go? 配置NHibernate以“随时随地”编译映射 - Configure NHibernate to compile mappings “on the go” 为什么有些安卓应用启动这么快? - Why do some android apps start so quickly? PostgreSQL是否可以快速搜索包含字符串数组的列? - Does PostgreSQL quickly search for columns with arrays of strings?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM