黎明灰烬 博客 +

FX!32:一个基于剖析的二进制翻译器

review on FX!32: A Profile-Directed Binary Translator

FX!32 是一个可以在 Alpha 平台上高效运行 X86 Windows 应用的新发明

引言

Digital公司的Alpha是世界上最快的处理器。虽然有部分要求高性能的软件被移植到了Alpha平台上,但还有很多X86软件不能在Alpha上使用。因此,Digital公司开发了可以在Alpha平台上以高效率运行X86应用的FX!32系统。

在不同体系结构上运行应用有两种方法:模拟和二进制翻译。模拟的效率较低但能真正“透明”地运行应用,而二进制翻译效率较高但不能实现透明计算。FX!32将两者结合起来在业界尚属首次。FX!32系统由三部分组成:一个支撑透明执行的运行时环境、一个高性能二进制翻译器和将这两种联合起来的服务。

FX!32是一个基于剖析的软件。第一次运行x86应用时完全使用模拟方法,但FX!32在软件运行时收集运行相关数据。在应用退出后,FX!32针对应用中的热点函数使用二进制翻译器将x86代码翻译成Alpha本地代码,从而替换掉原有的模拟方法。这样,尽管第一次运行会比较慢,后续的运行速度会非常快。这种“剖析-优化”还将一直持续下去。

基本框架

透明

FX!32的“透明运作”有两层含义:x86应用可以不加修改地运行、x86应用可以和本地应用交互操作。

启动x86应用会触发一个DLL——“透明代理”。在Windows上运行任何应用都会调用CreatProcess函数。通过拦截CreatProcess函数,“透明代理”可以甄别应用的平台,从而适时启动FX!32运行时环境来执行映像。尽管安装映像时FX!32时需要特权态授权,但以后运行映像时都不再需要。

FX!32将透明代理注入每个进程的地址空间。那么,当这些进程要执行x86映像时都会启动一个FX!32运行时环境。这样一来,透明代理将会以树状发展。很显然,用户登陆时会有一个“根”代理,这个“根”代理会启动其他的FX!32运行环境。

透明代理注入进程地址空间是通过应用的一系列调用入口完成的。详细说明参见Richter的 Advanced Windows NT 。透明代理使用的方法提供了一种可用于调整函数调用和本地API的通用方法。比如,对Win32 API的加载会转化为FX!32加载x86映像。同时,FX!32使得x86的某些函数可以被本地代码调用。透明代理对SPIKE也有特殊作用。

当用户运行x86应用时,Windows NT就会通过透明代理启动FX!32运行时。FX!32运行时的透明执行由一个支持完整x86用户指令集的模拟器实现。在应用第一次运行时,FX!32不知道哪些函数是热点函数,但下一次运行就可以用二进制翻译优化性能。FX!32透明性还由诸如多线程、结构化例外处理、跨x86-Alpha的COM结构等特性保证,从而实现完整的Win32环境。

运行时

FX!32的高性能来自于执行Alpha本地代码。模拟和二进制翻译的结合主要由运行时“透明地”将x86代码替换乘Alpha本地代码实现。任何尝试执行x86映像的操作会启动FX!32运行时。FX!32运行时将映像载入内存,设置运行时环境,并执行映像。

运行时加载器的功能等同于NT加载器。Alpha NT加载器在尝试执行不同结构的代码时会返回错误以指明被执行的是x86映像。这种方法免除了对NT体系的修改,但运行时加载器要冲定位这些信息:原有的基址、共享区、线程私有存储。

映像加载后,运行时加载器通过将指向映像的指针插入NT使用的表中,从而使得本地Windows NT代码可以区分x86和Alpha映像。幸运的是,这些表都是用户态的,不需要修改NT系统。不幸的是,这些表可能依赖于没有正式文档的Win32接口。这样一来,FX!32系统只支持特定版本的NT特性。

接下来,映像进入带有翻译后映像的FX!32数据库。访问数据库时使用映像文件头的hash值。这个哈希ID能唯一地定位映像,被运行时和协作服务使用。数据库主要记录x86代码和alpha代码的重定位信息,即x86-Alpha的PC映射。这样,当一个CALL操作发起时,运行时通过检查数据库可以知道该函数有没有被翻译。翻译后映像是由本地加载器加载的NT DLL。

剖析数据通过在运行时哈希表中插入计数完成。每次模拟指令时都会更新这个计数值。这样,在程序退出或者映像卸载时,协作服务会将本次运行的剖析数据和已有数据合并,从而决定是否启动翻译器。

跨体系结构

Win32应用会调用不属于应用自身的函数,比如Win32 API。NT-Alpha使用Alpha调用转换而非x86调用转换来支持这种调用。在管理本地Alpha函数和模拟/翻译代码时需要转换步骤。比如,x86函数通过栈传递参数,而Alpha通过寄存器传递参数。用于完成x86和Alpha之间转换的代码片段称作jacket

jacket主要有两种:静态和动态。静态jacket由加载时就明确了的接口定义,是FX!32运行时的一部分。大多数静态jacket使用文档和头文件自动生成。另一部分要通过特定方法传递参数的静态jacket是手工编写的。FX!32为Win32 API、NT调用返回机制、标准OLE对象和一些插件拓展提供静态jacket

COM对象的接口是在运行时动态“加壳(jacketed)”的。这些动态jacket使用从OLE库得到的类型信息创建。

本地API接口

Windows NT的大多数系统API都是操作系统的一部分。比如许多用户图形接口函数被集成到了NT系统DLL里。部分像Excel这样的应用运行时,有一半的时间都在使用库。因而为了尽量提高性能,FX!32要尽可能多地调用本地库。当NT加载器加载映像时会检测所有符号链接来定位要导入的例程和数据。FX!32运行时通过将入口指向正确的jacket入口来实现类似功能。jacket使用特殊的带有固定偏移的非法x86指令作为信号,告知模拟器要调用Alpha本地函数。jacket的基本操作就是按照Alpha规范调整函数调用参数,即将栈中的参数放到正确的寄存器中。有些jacket例程为被调用的本地例程提供特殊语义。例如GetSystemDirectoryjacket返回x86系统目录而不是真正的系统目录,这样本地Alpha动态链接库就不会被x86应用覆盖。

给Win32 API加壳

Digital的“预翻译实体”给系统和应用间的100多个系统调用接口加壳。Digital FX!32实现了所有的Windows NT定义和有正式文档的Win32 API,这是个浩大的工程。这些Alpha API要在保证正确性的前提下尽可能提高性能,因此它们都是本地API。Digital FX!32提供了超过50个本地Alpha DLL的静态jacket,其中还包括了很多没有正式文档的例程。现在,大约有12,000个例程被加壳了。

给回调例程加壳

许多Windows NT例程在发生特殊事件时会将例程的地址返回以方便回调。如果不加处理,Windows NT翻译后的Alpha代码会调用包含X86代码的例程,这陷入会导致应用崩溃。因此,我们给每个过程指针参数增加了一个静态jacketjacket的返回地址会被传递给本地Alpha代码。当Alpha代码回调其参数时,jacket会进入FX!32运行时。

给COM对象加壳

给COM加壳是最复杂的。COM对象是OLE函数指针表。这些函数的参数通常会有指向其他函数或者数据结构的指针。FX!32使用的方法可以被本地Alpha代码和X86代码使用。

给插件拓展加壳

为了实现交互性,本地x86插件也要尽可能支持。除了几个特殊的编程接口外,运行时对插件的支持还不完善。现有版本的FX!32给一部分通用插件接口加了壳,我们还在持续地改进这项功能。

运行时和后台优化器

一般的应用有多个可执行程序和调用映像。这些映像有些是应用私有的,有些是共享的。每次运行时加载x86映像时都会查询数据库查看该映像是否已被翻译成Alpha本地代码。这种高效的本地Alpha代码是在之前的模拟后由后台翻译器生成的。

在载入翻译后代码后,运行时建立x86代码和Alpha代码的对应地址表,然后,启动模拟器执行应用。这个小而精的模拟器根植于Alpha体系结构,充分利用了Alpha的64位寄存器和流水线特性挖掘性能。

在模拟x86映像时,运行时持续收集运行数据,以便后台优化器使用。FX!32的高性能来自于运行时和后台翻译器的良好协作。

将相关处理过程连接起来的服务

FX!32服务用于连接运行时和后台优化器。根据FX!32的默认配置和用户给出的参数,该服务维护着运行剖析数据,并适时地启动后台翻译器。当x86映像卸载后,该服务将新的剖析数据和以往数据合并,如果有新的翻译需求,则将这些需求加到后台翻译器的翻译队列中。

这个过程在每次执行映像时都会发生,下图展示了FX!32组件的执行流程。在两三次迭代后,剖析数据将趋于稳定,不会有新的翻译需求出现。这意味着“基本上”所有的执行路径都被翻译成了高效的本地Alpha代码。

execotion flow of FX!32

用二进制翻译加速

后台翻译器是一个可以生成高效本地Alpha代码的“第三代”基于剖析的二进制翻译器。它在映像执行完毕后被调用,以生成可以在下次运行映像时为运行时所用的本地代码。

设计目标

后台翻译器的工作和输出必须和运行时环境一样透明、健壮。这意味着用户不会意识到翻译器的存在,也无需干预翻译器的运行。为了保证这一点,设计翻译器时必须无需假设、无需初始化、无需用户介入,严格遵守透明性,无瑕疵地工作。

实现设计目标

为了确保透明、稳健,后台翻译器和运行时共同确保虚拟环境能准确代表x86机器状态,即能在任意观察点完整反应x86寄存器分配、调用返回边界和x86栈的状态。

同时,为了达到性能设计目标,翻译器要尽可能地应用针对可预测全局优化的现代编译优化技巧。以往的二进制翻译器基本上都是基于应用控制流图工作,它们被“(拓展)基本块”局限住了。而现代编译优化技术基于全局优化,这和二进制翻译器的基本块思想是相左的。因此,对于二进制翻译器来讲,消除这种冲突是提高性能的第一步。而FX!32的后台优化器会基于剖析数据谨慎选择基本块来组成大块——“翻译单元”。从概念上说,翻译单元和传统编译器的“例程”有些类似,因而可以使用全局优化技术深入挖掘性能。

基于剖析的二进制翻译器

Digital曾经使用过以静态二进制翻译为主的其他二进制翻译技术。(详见 SPIKE: An Optimizer for Alpha/NT Executables)为了尝试这些技术,以及硬件引擎和动态二进制翻译,我们花费了巨大的精力。

最终,我们开发的FX!32提出了和以往技术不同的全新系统。FX!32在模拟时收集应用的运行数据,然后启动二进制翻译器翻译热点例程。因为翻译器在后台运行,因而可以应用很多高级的优化算法。据我们所知,Digital FX!32是首次将环境模拟、剖析数据生成和二进制翻译结合起来的系统。为了区别于动态/静态二进制翻译,我们称该方法为基于剖析的二进制翻译。

正因为我们有剖析数据,不需要实现复杂的代码发现和控制流图算法,翻译器的实现才变得容易,只需要快速简单的查找。也因为剖许数据比控制流图要精确,生成的代码质量也比以往的静态二进制翻译器好,运行效率也高。

翻译器

虽然从多个角度来看,我们的二进制翻译器和传统的高性能编译器很相似,但它们依然有很多不同点。编译器总是将高级语言处理成低级语言,而二进制翻译器基于比特流中的低级语义指令,将其转换成控制流图。二进制翻译器的难点在于如何准确高效地从比特流中识别恢复出原始代码的控制流。

代码定位

剖析数据中记录的所有调用指令地址都会启动代码发现。在解析代码时,间接跳转的地址可以通过查找剖析文件得到。这样一来,就不再需要复杂且低速的数据迭代图。基于剖析的二进制翻译器以较少代码量就实现了比控制流更为精确的代码定位。

因为翻译器建立了一种优化版的控制流图,它可以把基本块连接为更大的单元。翻译器中包含一个重组器,它用来将x86映像分割为一系列例程。(具体可以参考 Region-Based Compilation: An Introduction and Motivation)这些翻译中使用的例程和源代码中的函数非常相似。

重组器的例程实际上是一系列region的集合。每个region是一个连续的地址段,这当中的地址可以被例程中任何地址入口访问。例程的返回点由剖析文件指定。和单入单出的基本块不同,region可以有多个入口。region的最小集合包含了能从例程入口开始执行的所有指令。大多数例程只有一个region。这种表示方法高效地描绘了将源映像拆分为翻译单元的方法。

(review:一般的基本块是基于二进制检查的,它从指令语义出发,保守地分割翻译单元。而FX!32的“例程”和region从实际执行路径出发,相对激进地分割翻译单元。FX!32的方案效率很高,至少看起来是这样。)

中间表示

翻译器的其他组件一次处理源映像的一个例程。所有控制流和剖析文件中记录的间接控制流都是明确的。每当类似间接分支的控制转移可能转移到未知目标时,翻译器就插入一个对模拟器的调用。只有例程的入口会被记录到x86-Alpha关系表中,这样就确保模拟器不会随意地在例程间跳转。

模拟器和翻译器共享一组权威的x86状态。在翻译器中,所有进入和退出例程时都使用严格的x86中间表示。除了这些例程的出入口之外,翻译器可以随意选择方便的方法来表示x86状态。对于静态二进制翻译器而言,在每个基本块间控制转移都会调用模拟器。因此,该翻译器的转换和优化不需要像静态二进制翻译器那样保守。基于此,翻译器可以针对整个例程执行全局转换和优化。

基于剖析的精确控制流图对翻译器的性能至关重要。在应用执行时,间接跳转到未执行过的目标时都会启动模拟器。在模拟器中,当直到其他例程被调用或者例程返回到翻译代码中的调用者时,翻译后代码执行才恢复。运行时会记录这样的事件。

x86和Alpha指令也有同样中间表示。中间表示从x86代码中创建例程开始,然后通过多种变换将x86语义模型转换到Alpha语义模型,再优化转换结果,最后Alpha代码被集成到翻译后映像中。

翻译和优化

我们的目标是支持大量的x86应用,而不仅仅是遵循NT调用方法的应用,因而FX!32要求很高的一致性。翻译器使用一个简单的代码生成器将x86指令转换成相对较长的Alpha指令序列,然后使用全局变换和优化技术来改进代码。

翻译器使用了很多传统编译器技术:死代码消除、常量传播、公共表达式替换、寄存器重命名、全局寄存器分配、指令调度和很多窥孔优化。

条件代码管理

很多x86指令都会生成条件指令标识位,但只有为数不多的指令使用这些标识。最初的x86模型中间表示中的每条指令都附带条件代码信息。因为全局数据流指明了每个x86条件代码的生命周期,只有在条件代码真正被使用时才会插入明确的用于生成条件标识的Alpha代码。

寄存器管理

x86体系结构会使用底层寄存器的不同字节。基于数据流将这些寄存器映射到Alpha寄存器,可以降低生成的Alpha代码体积。因为x86状态只有在例程边界上才是明确的,可以例程汇总对同一个x86寄存器不同字节的操作映射到独立的Alpha寄存器,从而提高访问效率。这种方法还使减少寄存器依赖的寄存器重命名成为可能。进一步地,寄存器重命名可以帮助指令调度。

栈管理

x86只有少量的寄存器,因而需要通过栈来保存临时数据。翻译器会分析访存行为以识别出针对栈的读写操作。翻译器假定所有被弹出栈的数据都不会被再次使用,从而消除不必要的栈上读写操作。但任何无法被化名的读写操作都不会被消除。因为运行时协议确保了栈指针之上(之下?)的数据不会被访问,在消除读写操作后,翻译器可以通过调整x86栈指针的变化来减少栈更新,。

例程管理

x86例程会查找栈并修改调用者的局部变量,包括返回地址。为了保证这些例程正常工作,FX!32需要让栈(对应确切x86状态的栈)对x86可见。CALL指令翻译后会在x86栈上保存x86返回地址,然后调用例程的翻译后代码。在此之后,x86返回地址在x86栈上,对应的本地返回地址在Alpha寄存器中。通常来说,例程不会修改返回地址,翻译后代码只要弹出x86栈,再以本地返回地址为目标执行本地返回。然而,这存在两个问题。首先,要鉴别应用是否修改过x86返回地址。其次,要保留用于存储本地返回地址的空间。这两个问题通过影子栈解决。

由翻译后代码和模拟器维护的影子栈维护本地Alpha栈的顶端。在被调用时,影子栈帧保存x86返回地址、Alpha返回地址和x86栈指针。RET指令的翻译后代码通过检查这些值来确定能否执行本地返回。如果不能,则立刻以x86返回地址为入口启动模拟器。在模拟RET指令时,模拟器会检查影子栈中的Alpha返回地址来确认是否有翻译后代码可用。

模拟器读取影子栈中的x86栈指针,然后删除所有在当前x86栈指针之上的影子栈帧。这种删除栈帧的情况会在收缩x86栈,返回更早的调用者时出现(就像C库中的longjmp例程那样)。为了确保影子栈不会溢出,这种清理工作会在模拟器使用影子栈之前完成。

其他可选方案

如上文所述,我们设计FX!32的基本目标时透明性和高性能。在实现结合了模拟、剖析生成和基于剖析二进制翻译的FX!32之前,我们参考了一系列的可选方案。(Some Efficient Architecture Simulation TechniquesShade: A Fast Instruction-Set Simulator for Execution ProfilingEmulation: RISC’s Secret Weapon

可选方案

基于硬件的方案

其中一种方案是设计一个全新的同时支持Alpha和x86的芯片。最常见的类似技术是称作“去耦合微体系结构”的混合设计。这种设计将高性能处理器核和x86指令译码器结合起来,译码器负责将x86指令翻译成更简单更利于快速执行的微操作。比如AMD K6、Intel Pentium Pro和NexGen Nx586。这些处理器都没有将可选指令集(内部微码指令集)开放给用户,这样就承受了CISC设计的惩罚(性能方面?),尽管它们的核心是RISC结构。这一点可以由同时支持Alpha/x86指令集的芯片克服,因为我们会将两种指令集向用户公开。不过,我们认为,即使是纯软件设计的Digital FX!32也可以为x86应用提供可观的性能,同时还避免了在Alpha处理器中为x86指令集提供支持可能带来的过高的硬件设计复杂度。

基于软件的方案

在一个ISA上运行另一个ISA的应用通常有两种可选的软件方法:模拟和二进制翻译。

模拟器

模拟器是在运行时动态执行源ISA指令的程序。很多系统都通过使用模拟器成功地运行为其他平台编写的应用。(参考 Rehosting Binary Code for Software Portability)模拟器的优势是透明,劣势是性能较差。比如,我们的用Alpha汇编语言精心设计的x86模拟器会在模拟一条x86指令需要大约45条Alpha指令,即模拟一条Pentium Pro微指令需要大约30条Alpha指令。如果模拟器很少使用,那这种结果还可以接受,但这显然和我们的性能设计相差甚远。

模拟器通常通过两种方式部署:部署在一个特定的约束环境中,或者和操作系统紧密结合。在约束环境中部署时,用户会打开一个模拟器窗口,所有的应用都在这个窗口内运行。这种方式违背了透明运行的宗旨。

在部署到集成系统时,操作系统加载器会在执行任何需要模拟应用时自动启动模拟器。在Windows NT第一次被移植到RISC平台时,它就带有一个可以运行16位x86应用的模拟器。因为Windows NT不是我们开发的,我们设计了一套不需要改动Windows NT就能运行x86应用的模拟器。

二进制翻译器

二进制翻译器是一类可以将源代码翻译成可直接执行的本地代码的程序。它的主要优点就是性能高。比如,在翻译之后,Digital FX!32平均使用4.4条Alpha指令来替代一条x86指令,即可用2.1条Alpha指令来替代一条Pentium Pro微操作。因为Alpha处理器的时钟速度(500-600 MHz)大约是x86处理器(166-233 MHz)的两倍,二进制翻译器基本上可以达到我们的性能要求。

在我们设计Digital FX!32之前,已经有两类二进制翻译技术:动态二进制翻译和静态二进制翻译。

动态二进制翻译器

为了提高性能,一些模拟器使用了二进制翻译技术,有时也称之为“即时翻译”,即JIT。(Efficient Implementation of the Smalltalk-80 System)这种方案在执行时翻译应用中的小片段。采用了二进制翻译的系统要在翻译获得的性能优势和翻译带来的时间开销间作好平衡。因为如果在翻译相关的操作花费太多时间会降低应用的响应速度,否则性能又不高。

因而,大多数这样的系统都严格控制优化深度,从而降低翻译开销。动态翻译器通常是无状态的,每次应用开始运行时都是全新的执行。应用的每次执行都相当于给动态翻译器提供了一个训练集。对于只运行一次的代码来说,这是一种比较有吸引力的方案,但对热点应用来说,重复启动开销太浪费了。

静态二进制翻译器

另一种可选的软件方案是静态翻译。静态翻译器一次将整个映像都翻译到目标机器。我们曾经设计过几个静态二进制翻译器,开发者和经验老到的用户认为这些翻译器可以方便快捷地迁移应用。当我们没有应用的源代码或者重新编译应用显得过于复杂,当应用并不需要极高的性能时,静态二进制翻译显得非常有效。

静态二进制翻译器的工作

用户需要手动使用翻译工具将非Alpha平台的应用代码转换成Alpha代码。这种方式在面对包含大量映像的应用时显得很笨拙,因为每个映像的翻译都需要用户手动操作。用户其实希望应用们能够“自然”地运行。

静态翻译器使用静态手段区分映像中代码数据,并识别控制流图。

静态翻译器通过以下几个步骤将映像分割成基本块:

  1. 静态翻译器识别出一系列被认作是基本块起始地址。这些地址遵循几个标准:它们对映像中的代码段可见,它们是一个入口或是重定位的目标,它们是一个分支中有效指令队列的起始地址。
  2. 静态翻译器分析识别出来的基本块,找到分支结束点,再尝试定位分支的目标。这些分支目标被当作新基本块的入口。对于某些分支操作来说,寻找分支目标是很简单的。但对于类似寄存器间接跳转的分支操作来说,它们需要跨例程的全局数据流。一个已经被识别的基本块的指令序列的某条指令被识别为入口是完全可能的。这意味着翻译器要不断地计算寄存器可能的值来建立数据流,不断地查找间接分支来分析基本块。
  3. 因为计算数据流时会丢失很多可能的值,静态翻译器要在代码段中搜寻遗漏的基本块。这些基本块不是已有基本块的比特序列,但可以被解析为分支的有效指令序列。

在这个过程完成后,静态翻译器得到控制流和源映像中类似基本块起始地址的地址表。当这些基本块都翻译完成后,地址表会拓展成“相连表”。表中存储的是原机器代码地址和其对应的翻译后代码地址。

在运行时,间接分支被翻译成一个对库例程的调用。这个例程在相连表中查找分支目标。存在对应的条目意味着基本块被翻译成了本地Alpha代码,例程会跳转到对应的翻译后基本块。否则,例程模拟下一次分支之后再尝试查找相连表(???)。

因为模拟器可以进入相连表中的所有翻译后基本块,优化往往被限制在块内。但块间的优化可以从相连表中移除的不必要的块。当然,不在控制流图内的块不能应用全局优化。

分析静态翻译器

尽管我们想使用像重复计算全映像数据流这样的高开销的方法来改进翻译,静态翻译器还是会丢失关键的控制流边,发现不会被使用的控制流边。相连表中也可能被错误地识别为基本块起始的数据,也有可能不包含间接跳转的目标基本块。

静态翻译器的性能依赖于解析间接跳转目标的效率。当开始设计Digital FX!32时,我们发现大多数x86应用使用的编程风格使得解析这些间接跳转目标变得很困难。因为运行前要手动地转换映像,静态翻译器也不够透明。这些因素使得我们最终设计了和能生成剖析数据的模拟器协同工作的基于剖析的翻译器。

FX!32的缺陷

Digital FX!32的不透明主要体现为x86软件安装时使用x86应用管理器,而在Alpha平台安装软件时要显式地使用类似的NT应用管理器。还有一点是,应用首次运行的速度要远比第二次运行慢。

有些问题在FX!32的初始版本中没有考虑。FX!32只能执行应用,不能执行驱动,因而需要一个负责管理连接到Alpha系统的外设的本地驱动。FX!32也无法提供NT控制面板中所有的x86 NT服务,因为它们在启动了FX!32服务之后才能使用。我们会在FX!32的后续版本中增加这些支持。

FX!32也不支持NT调试API。支持这些接口需要能够精确重放每一条x86指令的x86状态,这严重制约了翻译器能够优化的深度。这种权衡和编译器在可调试性和优化性能间作出的权衡类似。因为FX!32不支持调试接口,需要这些接口的应用不能在FX!32上运行。这些应用大多数是x86开发环境,我们觉得还是在真正的x86环境下运行最好。

性能

下图展示了在200MHz Pentium和500MHz Alpha处理器上同样配置的相对性能。FX!32的性能显然是很好的。我们在Alpha平台上以一致的数据输入,测量运行应用所耗费的时间。对于这些来自知名PC测试 BapCo SysMark 32 的测试,在Alpha的FX!32环境中运行时达到了和200MHz Pentium相近的性能。

performance of FX!32

在性能方面还有至少两点需要改进:后台优化器的工作要能自动执行而非被调度,优化器生成的代码性能还不尽如人意。

REVIEW

Digital真是一家神奇的公司!

FX!32可以看作是一个针对x86应用提供兼容的二进制翻译器。可以想象,为异构处理器、异构操作系统提供应用兼容是非常困难的。这其中包含着巨大的工程量。FX!32的特点是基于剖析,即不是单纯的静态翻译,也不是模拟出一套x86环境。这种“基于剖析的翻译”设计拥有动态二进制翻译器的最大特点——在运行时提高性能。而它的翻译是“离线的”,不会干扰到应用的响应速度,可以应用高级优化算法。这意味着FX!32集合了静态/动态二进制翻译两者的特点。当然,其缺点是第一次运行应用时是纯模拟,性能很低。从文中看,模拟器在运行x86应用时似乎收集了大量的剖析数据,这在某种程度上可能会进一步降低模拟器的速度。这么说呢,这种方案确实很厉害,但作为商业应用,工程代价还是有点略高……

虽然说是所谓的“基于剖析的翻译器”,FX!32还是要解决x86和RISC体系结构之间的矛盾,诸如寄存器数量、调用栈的管理等。为了实现异构模拟,FX!32也要重视自修改代码、分支目标查找、虚拟机状态维护等。总体上来说,FX!32最大的特点还是“离线动态翻译”,相信Digital公司的天才科学家和工程师们为此付出了巨大的努力。

黎明灰烬 博客

Creative Commons License 信箱:i(at)jackwish.net

计算机

清 谈