我在谷歌上搜索了一下Go的网站,但我找不到Go非凡的构建时间的解释。它们是语言特性(或缺乏特性)的产物、高度优化的编译器还是其他什么?我并不是在推广围棋;我只是好奇。
依赖性分析。
去常见问题解答通常包含以下句子:
Go为软件提供了一个模型 产生依赖性的构造 分析简单,避免了很多 c风格include文件的开销和 库。< / p >
虽然这个短语已经不在常见问题解答中了,但这个主题在从谷歌开始中进行了详细阐述,它比较了C/ c++和Go的依赖分析方法。
这是快速编译的主要原因。这是设计出来的。
编译效率是主要的设计目标:
最后,它的目的是要快:在一台计算机上构建一个大型可执行文件最多只需要几秒钟。为了实现这些目标,需要解决一些语言问题:表达性强但轻量级的类型系统;并发性和垃圾回收;刚性依赖规范;等等。常见问题解答
关于与解析相关的特定语言特性,语言常见问题解答非常有趣:
第二,该语言被设计成易于分析,并且可以在没有符号表的情况下进行解析。
简单(用我自己的话说),因为语法非常容易(分析和解析)
例如,没有类型继承意味着不需要进行有问题的分析,以确定新类型是否遵循基类型施加的规则。
例如,在这个代码示例:< em > < / em >“接口”中,编译器在分析给定接口的类型时,不会检查预期的类型实现是否存在。只有在它被使用之前(如果它被使用)才会执行检查。
另一个例子,编译器会告诉你你是否声明了一个变量而没有使用它(或者如果你应该持有一个返回值而你没有)
以下代码不能编译:
package main func main() { var a int a = 0 } notused.go:3: a declared and not used
这种类型的强制和原则使结果代码更安全,并且编译器不必执行程序员可以执行的额外验证。
总的来说,所有这些细节使语言更容易解析,从而导致快速编译。
再一次,用我自己的话来说。
编译的基本思想实际上非常简单。原则上,递归下降解析器可以以I/O限制的速度运行。代码生成基本上是一个非常简单的过程。符号表和基本类型系统并不需要大量的计算。
然而,降低编译器的速度并不难。
如果有一个预处理器阶段,具有多级包括指令、宏定义和条件编译,尽管这些东西很有用,但加载它并不难。(举个例子,我想到了Windows和MFC头文件。)这就是为什么预编译头文件是必要的。
在优化生成的代码方面,可以向该阶段添加多少处理是没有限制的。
虽然上面的大部分都是正确的,但有一点非常重要,没有被真正提到:依赖管理。
Go只需要包含你正在导入直接的包(就像那些已经导入的他们需要的一样)。这与C/ c++形成鲜明对比,在C/ c++中每一个文件开始包括x头文件,其中包括y头文件等。总结:Go的编译需要线性时间w.r.t到导入包的数量,而C/ c++需要指数级的时间。
我认为不是Go编译器是快,而是其他编译器是慢。
C和c++编译器必须解析大量的头文件——例如,编译c++“hello world”需要编译18k行代码,这几乎是半兆字节的源代码!
$ cpp hello.cpp | wc 18364 40513 433334
Java和c#编译器在VM中运行,这意味着在它们编译任何东西之前,操作系统必须加载整个VM,然后必须将它们从字节码编译为本机代码,所有这些都需要一些时间。
编译的速度取决于几个因素。
有些语言被设计为快速编译。例如,Pascal被设计为使用单遍编译器进行编译。
编译器本身也可以优化。例如,Turbo Pascal编译器是用手工优化的汇编器编写的,结合语言设计,可以在286类硬件上运行非常快的编译器。我认为即使是现在,现代的Pascal编译器(例如FreePascal)也比Go编译器快。
围棋的设计就是要快,这一点已经体现出来了。
请注意,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代码(增量编译模式甚至更高效)。
测试编译器翻译效率的一个很好的方法是自编译:给定的编译器编译自己需要多长时间?对于c++来说,这需要很长的时间(几个小时?)相比之下,在现代机器[1]上,Pascal/Modula-2/Oberon编译器将在不到一个秒的时间内编译自己。
Go一直受到这些语言的启发,但这种效率的一些主要原因包括:
定义清晰的语法,在数学上是合理的,用于有效的扫描和解析。
一种类型安全的静态编译语言,使用独立编译与依赖和类型检查跨模块边界,以避免不必要的头文件的重读和其他模块的重新编译-而不是独立编译,如C/ c++,编译器不执行这种跨模块检查(因此需要一遍又一遍地重读所有这些头文件,即使是简单的一行“hello world”程序)。
高效的编译器实现(例如,单次传递,递归向下自顶向下解析)——当然,上面的第1点和第2点极大地帮助了它。
这些原则在20世纪70年代和80年代已经在Mesa、Ada、modala -2/Oberon和其他几种语言中被了解并完全实现,直到现在(在2010年代)才被应用到现代语言中,如Go(谷歌)、Swift (Apple)、c# (Microsoft)和其他几种语言中。
让我们希望这将很快成为常态,而不是例外。要实现这一目标,需要做到两件事:
首先,软件平台提供商,如谷歌,微软和苹果应该鼓励应用程序开发人员使用新的编译方法,同时允许他们重用现有的代码库。这就是苹果现在试图用Swift编程语言做的事情,它可以与Objective-C共存(因为它使用相同的运行时环境)。
其次,随着时间的推移,底层软件平台本身应该使用这些原则重新编写,同时在这个过程中重新设计模块层次结构,使它们不那么单一。这当然是一项艰巨的任务,很可能需要十年的时间(如果他们有足够的勇气去做的话——我对谷歌的情况一点也不确定)。
在任何情况下,是平台推动了语言的采用,而不是相反。
引用:
[1] http://www.inf.ethz.ch/personal/wirth/ProjectOberon/PO.System.pdf,第6页:"编译器在大约3秒内编译自己"。此报价为低成本Xilinx Spartan-3 FPGA开发板,时钟频率为25 MHz,主内存为1 MByte。从这一点可以很容易推断,对于运行在时钟频率远高于1ghz和几gb主内存的现代处理器来说,“不到1秒”(即比Xilinx Spartan-3 FPGA板强大几个数量级),即使考虑到I/O速度。早在1990年,Oberon运行在25MHz NS32X32处理器上,主存2-4 MBytes,编译器在几秒钟内就完成了自我编译。即使在当时,Oberon程序员也完全不知道编译器用于完成编译周期的等待概念。对于典型的程序,总是从触发编译命令的鼠标按钮上移开手指要比等待编译器完成刚刚触发的编译花费更多的时间。这是真正的即时满足,等待时间几乎为零。生成的代码的质量,尽管不能完全与当时可用的最好的编译器相提并论,但对于大多数任务来说都非常好,总体上是可以接受的。
引用Alan Donovan和Brian Kernighan的书“Go编程语言”:
Go编译明显比大多数其他编译语言快,即使从头开始编译也是如此。编译器的速度有三个主要原因。首先,所有导入必须显式地列在每个源文件的开头,这样编译器就不必读取和处理整个文件来确定其依赖关系。其次,包的依赖关系形成了一个有向无循环图,由于没有循环,包可以单独编译,也可以并行编译。最后,编译后的Go包的目标文件不仅记录了包本身的导出信息,还记录了它的依赖项。当编译一个包时,编译器必须为每个导入读取一个object文件,但不需要查看这些文件以外的内容。
还有什么?