语言处理器的黑盒理解
编译器
就是这样一个程序: 输入:源程序 -> [编译器] -> 输出:目标程序,如下图1,并可以报告翻译过程中的错误。
如果目标程序是machine code,就可以被用户调用,处理输入产生输出:
解释器
与编译器区别是:并不翻译成目标程序,从用户角度看,可以直接运行由输入产出输出,关键是这里的解释和执行是同时的,而不是分为两个阶段。
解释器需要逐个语句执行源程序,比如一个for内的循环需要被解释多次;相对于编译后的机器指令,执行效率低10倍左右。
java语言处理器综合了编译和解释过程(还有JIT编译优化),是一个混合编译器:
这里的中间程序就是Bytecode,虚拟机会再根据具体的平台翻译成相应的machine code执行,所以这个编译的bytecode时可以拿到任何一个平台去run的。
生成目标可执行程序的其它语言处理器
过程如下,这里的编译会生成汇编代码,而汇编器会生成机器代码,需要进一步和其它可重定位的库等链接起来,不再解释:
编译器的结构
编译器分为分析部分和综合部分。分析部分要生成中间表示和符号表。
分析过程首先会将源程序分解为多个组成要素,并为组成要素添加语法结构,这就是中间表示。符号表则存储了源程序的变量名、作用域等信息。
整个编译过程如以下步骤构成:
AOT 与 JIT
开始提到的编译和解释两种常见的运行方式下,分别对应AOT于JIT。
尽管通过 JIT 编译保持了平台无关性,但是付出了一定代价。因为在程序执行时进行编译,所以编译代码的时间将计入程序的执行时间。任何编写过大型 C 或 C++ 程序的人都知道,编译过程往往较慢。
为了克服这个缺点,现代的 JIT 编译器使用了下面两种方法的任意一种(某些情况下同时使用了这两种方法)。第一种方法是:编译所有的代码,但是不执行任何耗时多的分析和转换,因此可以快速生成代码。由于生成代码的速度很快,因此尽管可以明显观察到编译带来的开销,但是这很容易就被反复执行本地代码所带来的性能改善所掩盖。第二种方法是:将编译资源只分配给少量的频繁执行的方法(通常称作热方法)。低编译开销更容易被反复执行热代码带来的性能优势掩盖。很多应用程序只执行少量的热方法,因此这种方法有效地实现了编译性能成本的最小化。
动态编译器的一个主要的复杂性在于权衡了解编译代码的预期获益使方法的执行对整个程序的性能起多大作用。一个极端的例子是,程序执行后,您非常清楚哪些方法对于这个特定的执行的性能贡献最大,但是编译这些方法毫无用处,因为程序已经完成。而在另一个极端,程序执行前无法得知哪些方法重要,但是每种方法的潜在受益都最大化了。大多数动态编译器的操作介于这两个极端之间,方法是权衡了解方法预期获益的重要程度。