我在谷歌上搜索并查找了Go网站,但是我无法找到关于Go不同寻常的编译时间的解释。它们是语言特性(或缺乏特性)、高度优化的编译器还是其他原因导致的?我并不想宣传Go,只是好奇。
我在谷歌上搜索并查找了Go网站,但是我无法找到关于Go不同寻常的编译时间的解释。它们是语言特性(或缺乏特性)、高度优化的编译器还是其他原因导致的?我并不想宣传Go,只是好奇。
依赖分析。
Go 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#编译器运行在虚拟机中,这意味着在它们可以编译任何东西之前,操作系统必须加载整个虚拟机,然后它们必须从字节码JIT编译成本地代码,所有这些都需要一些时间。
编译速度取决于几个因素。
有些语言被设计为编译速度快。例如,Pascal是使用单通道编译器进行编译的。
编译器本身也可以进行优化。例如,Turbo Pascal编译器是用手写优化汇编语言编写的,与语言设计相结合,可在286级硬件上运行非常快的编译器。我认为,即使现在,现代的Pascal编译器(如FreePascal)也比Go编译器更快。
Go编译器比大多数C/C++编译器快得多的原因有很多:
最主要的原因:大多数 C/C++ 编译器在编译速度方面设计得非常糟糕。此外,从编译速度的角度来看,C/C++ 生态系统中的某些部分(例如程序员编写代码的编辑器)并没有考虑到编译速度。
最主要的原因:快速编译速度是 Go 编译器和 Go 语言有意为之的选择。
与 C++ 不同,Go 没有模板和内联函数。这意味着 Go 不需要执行任何模板或函数实例化。
Go 编译器比 C/C++ 编译器具有更简单的优化器。
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 代码(增量编译模式甚至更有效率)。
编译效率是主要的设计目标:
最终,旨在实现快速构建:在单台计算机上构建大型可执行文件最多只需要几秒钟时间。为了达到这些目标,需要解决许多语言问题:一个富有表现力但轻量级的类型系统; 并发和垃圾回收; 严格的依赖说明等等。常见问题
关于与解析相关的具体语言特性,语言常见问题非常有趣:
其次,该语言被设计为易于分析并且可以在没有符号表的情况下解析。
aType
是一个变量引用,并且在语义分析阶段发现它不是时,你会在那时打印一个有意义的错误信息。 - Sam Harwell虽然上面的大部分是正确的,但有一个非常重要的点没有被提到:依赖管理。
Go只需要包含您直接导入的软件包(因为它们已经导入了它们所需的内容)。这与C/C++形成了鲜明对比,因为每个单独的文件都开始包含x头文件,而这些头文件又包含y头文件等等。总之,Go的编译时间与导入的软件包数量成线性关系,而C/C++则需要呈指数增长。
清晰定义的语法,数学上严谨,便于高效扫描和解析。
类型安全和静态编译语言,使用带有依赖和跨模块边界类型检查的分离编译,以避免不必要的重读头文件和重新编译其他模块 - 与C/C++中的独立编译相反,编译器不执行此类跨模块检查 (因此需要一遍又一遍地重读所有这些头文件,即使对于一个简单的一行"hello world"程序)。
高效的编译器实现(例如单遍、递归下降自顶向下解析)——当然,以上的第1和第2点会大大帮助这一点。
这些原则已经在20世纪70年代和80年代的语言中得到充分实现,例如 Mesa、Ada、Modula-2/Oberon 等等,并且现在(在2010年代)正被引入到现代语言中,例如 Go (Google)、Swift (Apple)、C#(Microsoft) 等等。
让我们希望这将很快成为常规而不是例外。要达到这个目标,需要发生两件事:
首先,软件平台提供者如 Google、Microsoft 和 Apple 应该开始鼓励应用程序开发者使用新的编译方法,同时使他们能够重用他们现有的代码库。这就是苹果现在试图做的事情,他们正在使用 Swift 编程语言,它可以与 Objective-C 共存(因为它使用相同的运行时环境)。
其次,这些原则最终应该被用于重新编写底层软件平台,并在此过程中重新设计模块层次结构,以使它们不再是单olithic的。当然,这是一项艰巨的任务,可能需要大部分十年左右的时间(如果他们足够勇敢去实现它 - 我对谷歌这样做并不确定)。
无论如何,是平台推动了语言的采用,而不是相反。
参考文献:
[1] http://www.inf.ethz.ch/personal/wirth/ProjectOberon/PO.System.pdf,第6页:“编译器自身的编译大约需要3秒钟”。这个引用是针对低成本的Xilinx Spartan-3 FPGA开发板,在25MHz的时钟频率下运行,并拥有1MB的主存储器。从这个引用可以轻松地推断出,“ less than 1 second ”适用于现代处理器,它们的时钟频率远高于1 GHz,并且具有几GB的主内存(即比Xilinx Spartan-3 FPGA板强大几个数量级),即使考虑到I/O速度也是如此。即使在1990年,当Oberon在一台25MHz的NS32X32处理器上运行,并配有2-4MB的主内存时,编译器自身也只需要几秒钟就可以完成编译。即使那时候,等待编译器完成编译周期的概念对于Oberon程序员来说也是完全未知的。对于典型的程序,从触发编译命令到等待编译器完成编译而要花费的时间是总是比从鼠标按钮中删除手指所需的时间更长。这是真正的即时满足,几乎没有等待时间。尽管产生的代码质量并不总是完全达到当时最好的编译器的水平,但对于大多数任务来说,它已经相当好,一般情况下也是可以接受的。
Go旨在快速设计,而它也确实表现出了这一点。
请注意,Go不是唯一具有这些功能的语言(模块在现代语言中是常见的),但它们做得很好。
简单来说,因为语法非常易于分析和解析。
例如,没有类型继承意味着无需繁琐的分析以查找新类型是否遵循基础类型所强制的规则。
例如,在此代码示例中:"interfaces"编译器在分析该类型时不会去检查预定类型是否实现了给定接口。只有在使用它(并且如果使用了它)时才执行检查。
另一个例子是,编译器会告诉您是否声明了一个变量但未使用它(或者如果您应该保存返回值但未使用)。
以下代码无法编译:
package main
func main() {
var a int
a = 0
}
notused.go:3: a declared and not used