我已经在Go网站上搜索了Google,但在Go的非凡构建时间上似乎找不到解释。它们是语言功能(或缺少语言功能),高度优化的编译器或其他产品的产物吗?我不是要推广Go;我只是好奇。
我已经在Go网站上搜索了Google,但在Go的非凡构建时间上似乎找不到解释。它们是语言功能(或缺少语言功能),高度优化的编译器或其他产品的产物吗?我不是要推广Go;我只是好奇。
Answers:
依赖性分析。
在转到FAQ用于包含下面的句子:
Go提供了一种用于软件构建的模型,该模型使依赖关系分析变得容易,并避免了C样式包含文件和库的大量开销。
虽然该短语不再是FAQ中的内容,但在Google的Go讨论中对该主题进行了详细说明,该演讲比较了C / C ++和Go的依赖关系分析方法。
这是快速编译的主要原因。这是设计使然。
我认为不是Go编译器很快,而是其他编译器很慢。
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编译器更快。
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代码(并且增量编译模式效率更高)。
编译效率是主要设计目标:
最后,它旨在快速:在单台计算机上构建大型可执行文件最多需要几秒钟。为了实现这些目标,需要解决许多语言问题:一个富有表现力但轻巧的字体系统;并发和垃圾回收;严格的依赖规范;等等。常问问题
关于与解析相关的特定语言功能,语言FAQ非常有趣:
其次,该语言的设计易于分析,无需符号表即可进行解析。
aType
是一个变量引用一样,稍后在语义分析阶段,发现它不是您当时要打印有意义的错误。
自编译是对编译器转换效率的一个很好的测试:自定义编译器需要多长时间进行编译?对于C ++,它需要很长时间(几小时?)。相比之下,Pascal / Modula-2 / Oberon编译器可以在一台现代机器上不到一秒钟的时间自行编译[1]。
Go受到了这些语言的启发,但是提高效率的一些主要原因包括:
明确定义的语法在数学上是合理的,可进行有效的扫描和解析。
一种类型安全且静态编译的语言,使用独立的编译功能并跨模块边界进行依赖和类型检查,以避免不必要的重新读取头文件和其他模块的重新编译,这与C / C ++中的独立编译相反编译器不会执行此类跨模块检查(因此,即使对于简单的单行“ hello world”程序,也需要一遍又一遍地重新读取所有这些头文件)。
高效的编译器实现(例如,单遍,递归下降自上而下的解析)-上面的第1点和第2点当然可以极大地帮助您。
这些原则已在1970年代和1980年代以Mesa,Ada,Modula-2 / Oberon等语言和其他几种语言广为人知并得到了全面实施,并且直到现在(2010年代)才逐渐被Go(谷歌)等现代语言所采用。 ,Swift(Apple),C#(Microsoft)等。
我们希望这将很快成为规范,而不是例外。要到达那里,需要发生两件事:
首先,诸如Google,Microsoft和Apple之类的软件平台提供商应首先鼓励应用程序开发人员使用新的编译方法,同时使他们能够重用其现有代码库。这就是Apple现在尝试使用的Swift编程语言,它可以与Objective-C共存(因为它使用相同的运行时环境)。
其次,底层软件平台本身最终应使用这些原理随着时间的流逝而重新编写,同时在流程中同时重新设计模块层次结构,以减少它们的整体性。当然,这是一项艰巨的任务,并且可能会花费十年的大部分时间(如果他们有足够的勇气真正做到这一点-对于Google,我完全不确定)。
无论如何,推动语言采用的是平台,而不是相反。
参考文献:
[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 Spartan-3 FPGA板强大几个数量级)的现代处理器,可以很容易地将其推断为“不到1秒”,即使考虑到I / O速度。早在1990年,Oberon在具有2-4 MBytes主内存的25MHz NS32X32处理器上运行时,编译器便在几秒钟内完成了编译。实际等待的概念当时甚至对于Oberon程序员来说,编译器完成编译周期都是完全未知的。对于典型程序,从等待触发编译命令的鼠标按钮移开手指的时间通常比等待编译器完成刚刚触发的编译要花费更多的时间。真正的即时满足感,等待时间几乎为零。而且,尽管所生成的代码的质量始终不能与当时的最佳编译器完全相提并论,但它对于大多数任务而言还是非常出色的,并且通常是可以接受的。
Go的设计速度很快,并且可以证明。
请注意,GO并不是唯一具有这种功能的语言(模块是现代语言中的规范),但是它们做得很好。
引用艾伦·多诺万(Alan Donovan)和布莱恩·科尼根(Brian Kernighan)的著作《The Go Programming Language》:
即使是从头开始构建,Go编译也比大多数其他编译语言要快。编译器速度的三个主要原因。首先,必须在每个源文件的开头明确列出所有导入,因此编译器不必读取和处理整个文件即可确定其依赖性。其次,包的依赖性形成有向无环图,并且由于没有循环,因此包可以分别编译,也可以并行编译。最后,已编译的Go软件包的目标文件不仅记录软件包本身的导出信息,而且还记录其依赖项的导出信息。编译软件包时,编译器必须为每个导入读取一个目标文件,但不必超出这些文件。
简单(用我自己的话),因为语法非常容易(分析和解析)
例如,没有类型继承意味着没有问题的分析来确定新类型是否遵循基本类型强加的规则。
例如,在以下代码示例中:“ interfaces”编译器在分析该类型时不会去检查目标类型是否实现了给定的接口。仅在使用它之前(如果使用了它,则在使用之前)进行检查。
在另一个示例中,编译器会告诉您是否声明了变量并且未使用它(或者是否应该保留返回值而您没有)
以下内容无法编译:
package main
func main() {
var a int
a = 0
}
notused.go:3: a declared and not used
这种强制性和原则使生成的代码更安全,并且编译器不必执行程序员可以执行的额外验证。
总的来说,所有这些细节使语言更易于解析,从而可以快速进行编译。
再用我自己的话说。
还有什么?