Go如何快速编译?

时间:2010-06-04 18:10:13

标签: performance compiler-construction build-process go

我在Go网站上搜索并搜索过,但我似乎无法找到Go的非凡构建时间的解释。它们是语言功能(或缺少),高度优化的编译器或其他东西的产品吗?我不是想推广Go;我只是好奇。

12 个答案:

答案 0 :(得分:177)

依赖性分析。

来自Go FAQ

  

Go提供了一个软件模型   建设,使依赖   分析容易,避免了很多   C风格的开销包括文件和   库。

这是快速编译的主要原因。这是设计的。

答案 1 :(得分:70)

我认为Go编译器不是 fast ,而是其他编译器

C和C ++编译器必须解析大量的头文件 - 例如,编译C ++“hello world”需要编译18k行代码,这几乎是半兆字节的源代码!

$ cpp hello.cpp | wc
  18364   40513  433334

Java和C#编译器在VM中运行,这意味着在它们可以编译任何东西之前,操作系统必须加载整个VM,然后它们必须从字节码到本机代码进行JIT编译,所有这些都需要一些时间。

编译速度取决于几个因素。

某些语言旨在快速编译。例如,Pascal被设计为使用单通道编译器进行编译。

编译器本身也可以进行优化。例如,Turbo Pascal编译器是用手动优化的汇编程序编写的,结合语言设计,可以在286级硬件上运行一个非常快速的编译器。我认为即使是现在,现代的Pascal编译器(例如FreePascal)也比Go编译器快。

答案 2 :(得分:34)

Go编译器比大多数C / C ++编译器快得多的原因有多种:

  • 主要原因:大多数C / C ++编译器都表现出异常糟糕的设计(从编译速度的角度来看)。另外,从编译速度的角度来看,C / C ++生态系统的某些部分(例如程序员编写代码的编辑器)并没有考虑到编译速度。

  • 主要原因:快速编译速度是Go编译器以及Go语言中的有意识选择

  • Go编译器比C / C ++编译器具有更简单的优化器

  • 与C ++不同,Go没有模板,也没有内联函数。这意味着Go不需要执行任何模板或函数实例化。

  • Go编译器更快地生成低级汇编代码,优化器处理汇编代码,而在典型的C / C ++编译器中,优化传递工作在原始源代码的内部表示上。 C / C ++编译器的额外开销来自需要生成内部表示的事实。

  • Go程序的最终链接(5l / 6l / 8l)可能比链接C / C ++程序慢,因为Go编译器正在浏览所有使用过的汇编代码,也许它还在做其他的C / C ++链接器没有执行的额外操作

  • 一些C / C ++编译器(GCC)以文本形式生成指令(传递给汇编器),而Go编译器生成二进制形式的指令。为了将文本转换为二进制文件,需要进行额外的工作(但不是很多)。

  • Go编译器仅针对少量CPU架构,而GCC编译器针对大量CPU

  • 以高速编译为目标的编译器,如Jikes,速度很快。在2GHz CPU上,Jikes每秒可以编译20000多行Java代码(并且增量编译模式更加高效)。

答案 3 :(得分:32)

编译效率是一个主要的设计目标:

  

最后,它应该很快:在一台计算机上构建一个大型可执行文件最多需要几秒钟。为了实现这些目标,需要解决许多语言问题:一种富有表现力但轻量级的系统;并发和垃圾收集;严格依赖规范;等等。 FAQ

关于与解析相关的特定语言功能,语言常见问题解答非常有趣:

  

其次,该语言设计为易于分析,无需符号表即可解析。

答案 4 :(得分:23)

虽然上述大部分内容都属实,但有一个非常重要的问题并未真正提及:依赖管理。

只需要包含您直接导入的软件包(因为那些已导入他们需要的软件包)。这与C / C ++形成鲜明对比,其中每个文件开始包括x标头,其中包括y标头等。底线:Go的编译需要线性时间wrt到导入包的数量,其中C / C ++需要指数时间。

答案 5 :(得分:20)

编译器翻译效率的一个很好的测试是自编译:给定的编译器自己编译需要多长时间?对于C ++,它需要很长时间(几小时?)。相比之下,Pascal / Modula-2 / Oberon编译器可以在现代机器上以小于 1 秒的速度编译自己[1]。

Go受到这些语言的启发,但这种效率的一些主要原因包括:

  1. 一种明确定义的数学语法,用于高效扫描和解析。

  2. 一种类型安全且静态编译的语言,它使用单独的编译依赖关系并在模块边界上键入,避免不必要的重新读取头文件和重新编译其他模块 - 与C / C ++中的独立编译相反,其中没有编译器执行这样的跨模块检查(因此需要重复阅读所有这些头文件,即使是简单的单行“hello world”程序。

  3. 高效的编译器实现(例如单遍,递归下降自上而下解析) - 当然,上述第1点和第2点对此有很大帮助。

  4. 这些原则已经在20世纪70年代和80年代以Mesa,Ada,Modula-2 / Oberon等语言得到了充分实施,并且现在(仅在2010年)才开始进入现代语言,如Go (谷歌),斯威夫特(Apple),C#(微软)和其他几个。

    让我们希望这很快就会成为常态,而不是例外。要实现这一目标,需要做两件事:

    1. 首先,谷歌,微软和苹果等软件平台提供商应首先鼓励应用程序开发人员使用新的编译方法,同时使他们能够重用现有的代码库。这就是Apple现在尝试使用Swift编程语言,它可以与Objective-C共存(因为它使用相同的运行时环境)。

    2. 其次,底层软件平台本身应该最终使用这些原则重新编写,同时在流程中重新设计模块层次结构,使它们不那么单一。这当然是一项艰巨的任务,可能需要花费十年的时间(如果他们有足够的勇气去实际做到这一点 - 对于谷歌而言我完全不确定)。

    3. 无论如何,它是推动语言采用的平台,而不是相反的方式。

      参考文献:

      [1] http://www.inf.ethz.ch/personal/wirth/ProjectOberon/PO.System.pdf,第6页:“编译器在大约3秒内编译自己”。该报价适用于低成本Xilinx Spartan-3 FPGA开发板,其运行频率为25 MHz,主内存为1 MB。对于运行时钟频率远高于1 GHz的现代处理器和几GB的主存储器(比Xilinx强大几个数量级),可以轻松地外推到“不到1秒” Spartan-3 FPGA板),即使考虑到I / O速度。早在1990年Oberon运行在具有2-4 MB主内存的25MHz NS32X32处理器上时,编译器就在几秒钟内编译完成。编译器完成编译周期的实际等待的概念对于Oberon程序员来说是完全未知的。对于典型的程序,总是花费更多的时间从触发编译命令的鼠标按钮中移除手指,而不是等待编译器完成刚刚触发的编译。这是真正的即时满足,等待时间几乎为零。而且,生成的代码的质量虽然并不总是与当时可用的最佳编译器完全相同,但对于大多数任务来说非常好,而且一般来说都是可以接受的。

答案 6 :(得分:11)

Go设计得很快,并显示出来。

  1. 依赖管理:没有头文件,您只需要查看直接导入的包(无需担心它们导入的内容),因此您具有线性依赖性。
  2. 语法:语言的语法很简单,因此很容易解析。虽然减少了功能的数量,但编译器代码本身很紧凑(路径很少)。
  3. 不允许超载:您看到一个符号,您知道它引用了哪种方法。
  4. 通常可以并行编译Go,因为每个包都可以独立编译。
  5. 请注意,GO并不是唯一具有此类功能的语言(模块是现代语言中的标准),但它们做得很好。

答案 7 :(得分:8)

编译的基本思路实际上非常简单。原则上,递归下降解析器可以以I / O绑定速度运行。代码生成基本上是一个非常简单的过程。符号表和基本类型系统不需要大量计算。

但是,减慢编译器的速度并不难。

如果存在预处理器阶段,使用多级 include 指令,宏定义和条件编译,就像那些有用的那样,加载它并不困难。 (举个例子,我正在考虑Windows和MFC头文件。)这就是为什么需要预编译头文件。

在优化生成的代码方面,对该阶段可以添加多少处理没有限制。

答案 8 :(得分:6)

引自Alan Donovan和Brian Kernighan的书“The Go Programming Language”:

  

即使从头开始构建,Go编译也比大多数其他编译语言快得多。编译器的速度有三个主要原因。首先,必须在每个源文件的开头显式列出所有导入,因此编译器不必读取和处理整个文件以确定其依赖性。其次,包的依赖关系形成有向无环图,并且因为没有循环,所以包可以单独编译,也可以并行编译。最后,已编译的Go包的目标文件不仅记录包本身的导出信息,还记录其依赖性的导出信息。编译包时,编译器必须为每个导入读取一个目标文件,但不必超出这些文件。

答案 9 :(得分:4)

简单(用我自己的话说),因为语法很容易(分析和解析)

例如,没有类型继承意味着,没有问题分析,以确定新类型是否遵循基类型强加的规则。

例如,在此代码示例中:"interfaces"编译器在分析该类型时不会检查预期类型实现给定接口。只有在使用之前(如果使用它),才会进行检查。

其他示例,编译器会告诉您是否声明了一个变量并且没有使用它(或者如果你应该保留一个返回值但你不是)

以下内容无法编译:

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

这种强制执行和principles使得生成的代码更安全,编译器不必执行程序员可以执行的额外验证。

最重要的是,所有这些细节都使语言更容易解析,从而导致快速编译。

再次,用我自己的话说。

答案 10 :(得分:2)

我认为Go是与编译器创建并行设计的,所以他们从出生就是最好的朋友。 (IMO)

答案 11 :(得分:0)

  • 对所有文件都导入一次依赖项,因此导入时间不会随项目大小的增加而成指数增加。
  • 简单的语言学意味着解释它们需要较少的计算。

还有什么?