Eva*_*ske 209 compiler-construction performance build-process go
我用Google搜索并在Go网站上搜索,但我似乎无法找到Go的非凡构建时间的解释.它们是语言功能(或缺少),高度优化的编译器或其他东西的产品吗?我不是想推广Go; 我只是好奇.
el.*_*ado 73
我认为并不是Go编译器很快,而是其他编译器很慢.
C和C++编译器必须解析头的巨额 - 例如,编译C++"Hello World"的要求编制18K行的代码,这是源近半兆字节!
$ cpp hello.cpp | wc
18364 40513 433334
Run Code Online (Sandbox Code Playgroud)
Java和C#编译器在一个虚拟机,这意味着才可以编译任何东西,操作系统有加载整个虚拟机上运行,那么他们必须JIT编译的字节码为本地代码,所有这些都需要一定的时间.
编译速度取决于几个因素.
有些语言旨在快速编译.例如,Pascal被设计为使用单通道编译器进行编译.
编译器本身也可以进行优化.例如,Turbo Pascal的编译器是写在手优化汇编程序,它与语言设计相结合,产生了一个非常快的编译器上286级的硬件工作.我认为即使是现在,现代Pascal编译器(例如FreePascal)也比Go编译器快.
小智 38
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代码(并且增量编译模式更加高效).
Lar*_*ien 33
编译效率是一个主要的设计目标:
最后,它应该是快速的:在一台计算机上构建一个大型可执行文件最多需要几秒钟.为了实现这些目标,需要解决许多语言问题:一种富有表现力但轻量级的系统; 并发和垃圾收集; 严格依赖规范; 等等.常问问题
关于与解析相关的特定语言功能,语言FAQ非常有趣:
其次,该语言被设计为易于分析,并且可以在没有符号表的情况下进行解析.
小智 26
虽然上述大部分内容都是正确的,但有一个非常重要的一点并没有真正提及:依赖管理.
Go只需要包含您直接导入的包(就像那些已导入它们所需的包).这与C/C++形成鲜明对比,其中每个文件都包含x标题,包括y标题等.底线:Go的编译需要线性时间wrt到导入包的数量,其中C/C++需要指数时间.
小智 22
对编译器的转换效率的一个很好的测试是自编译:给定的编译器自己编译需要多长时间?对于C++,它需要很长时间(几小时?).通过比较,一个Pascal/Modula-2的/奥伯伦编译器将编译本身在不到之一秒的现代机器上[1].
Go受这些语言的启发,但这种效率的一些主要原因包括:
一种明确定义的语法,在数学上是合理的,用于有效的扫描和解析.
的类型安全和静态编译语言使用单独的编译与依赖性和类型检查跨越模块的界限,以避免的头文件和其它模块的重新编译不必要的重新读-而不是独立的在C/C++,其中汇编等编译器不执行这样的跨模块检查(因此需要一遍又一遍地重读所有这些头文件,即使对于简单的单行"hello world"程序也是如此).
一个有效的编译器实现(例如单通道,递归下降自上而下的解析) - 当然,上面的第1点和第2点对它们有很大帮助.
这些原则已经在20世纪70年代和80年代以Mesa,Ada,Modula-2/Oberon等语言得到了充分实施,并且现在(仅在2010年)才进入现代语言,如Go(谷歌) ,Swift(Apple),C#(微软)和其他几个.
让我们希望这很快就会成为常态,而不是例外.要实现这一目标,需要做两件事:
首先,谷歌,微软和苹果等软件平台提供商应首先鼓励应用程序开发人员使用新的编译方法,同时使他们能够重用现有的代码库.这就是Apple现在尝试使用Swift编程语言,它可以与Objective-C共存(因为它使用相同的运行时环境).
其次,底层软件平台本身应该最终使用这些原则重新编写,同时在流程中重新设计模块层次结构,使它们不那么单一.这当然是一项庞大的任务,可能需要十年的大部分时间(如果他们有足够的勇气去实际做到这一点 - 对于谷歌来说我完全不确定).
无论如何,它是推动语言采用的平台,而不是相反的方式.
参考文献:
[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 MB主内存的25MHz NS32X32处理器上时,编译器就在几秒钟内完成了编译.实际上等待编译器完成编译周期的概念对Oberon程序员来说是完全不为人知的.对于典型的程序,从触发编译命令的鼠标按钮移除手指总是花费更多的时间,而不是等待编译器完成刚刚触发的编译.这是真正的即时满足,等待时间几乎为零.并且所生产的代码的质量,即使并不总是与当时可用的最佳编译器完全相同,但对于大多数任务来说非常好,并且通常是可接受的.
Mat*_* M. 13
Go设计得很快,它显示出来.
请注意,GO不是唯一具有此类功能的语言(模块是现代语言中的标准),但它们做得很好.
Mis*_*ant 11
引自Alan Donovan和Brian Kernighan的" The Go Programming Language " 一书:
即使从头开始构建,Go编译也比大多数其他编译语言快得多.编译器的速度有三个主要原因.首先,必须在每个源文件的开头显式列出所有导入,因此编译器不必读取和处理整个文件以确定其依赖性.其次,包的依赖关系形成有向无环图,并且因为没有循环,所以包可以单独编译,也可以并行编译.最后,已编译的Go包的目标文件不仅记录包本身的导出信息,还记录其依赖性的导出信息.编译包时,编译器必须为每个导入读取一个目标文件,但不必超出这些文件.
编译的基本思想实际上非常简单.原则上,递归下降解析器可以以I/O绑定速度运行.代码生成基本上是一个非常简单的过程.符号表和基本类型系统不需要大量计算.
但是,减慢编译器的速度并不困难.
如果存在预处理器阶段,使用多级包含指令,宏定义和条件编译,就像那些事情一样有用,就不难加载它.(举个例子,我正在考虑Windows和MFC头文件.)这就是为什么需要预编译头文件.
在优化生成的代码方面,可以向该阶段添加多少处理没有限制.
很简单(用我自己的话),因为语法非常容易(分析和解析)
例如,没有类型继承意味着没有问题的分析来确定新类型是否遵循基本类型强加的规则。
例如在以下代码示例中:“ interfaces”编译器在分析该类型时不会去检查目标类型是否实现了给定的接口。仅在使用它之前(如果使用了它,则在使用之前)进行检查。
在另一个示例中,编译器会告诉您是否声明了变量并且未使用它(或者是否应该保留返回值而您没有)
以下内容无法编译:
package main
func main() {
var a int
a = 0
}
notused.go:3: a declared and not used
Run Code Online (Sandbox Code Playgroud)
这种强制执行和原则使生成的代码更安全,并且编译器不必执行程序员可以执行的额外验证。
总的来说,所有这些细节使语言更易于解析,从而可以快速进行编译。
再用我自己的话说。