写给编程语言,虚拟机,优化以及未来之路

来源:互联网 发布:cms监控软件怎么连手机 编辑:程序博客网 时间:2024/05/21 02:32

译自:By Charles Nutter at Headius: On Languages, VMs, Optimization, and the Way of the World


在过去的几年里,我研究了多种虚拟机之后,领悟了应该或不应该做哪些事才能使代码被正确地优化。这些思想可以应用到编程语言上,语言深层的结构上以及优化这些语言的虚拟机上。从我炯炯的双眼望去,发现在当今的优化技术之下,有着如下的铁律。

咱来瞅瞅:

#1 不一定非得采用静态类型系统(编译期类型系统)。

JVM和其它动态优化的运行时系统已经证明了这一点。在运行时,我们完全有可能获取到和编译期间通过静态类型系统所能看到的同样的信息,并且对代码做出不逊于静态类型加静态编译情况下的优化。在某些情况下,这可能会优化得更好,因为运行时的采样基于真正的执行过程,真正的分支概率,真正地代码行为而非编译时的猜测。你甚至可以声称静态优化等同于图灵停机问题(不可计算的),而因为动态优化总是优化程序实际上在执行的代码,所以它理论上讲应该是可以秒掉静态优化的。

然而,这需要把一件关键的事情做好。

#2 类型需要是可预知的

为了执行运行时优化,对象应该具有可预知的类型并且这些类型应该拥有可预知的结构。这并不是说类型必须是静态声明的……这只是要求类型本身不要变来变去。如果对象的类型可以改变(Smalltalk的become, perl和C里的弱类型),你就必须增加更多的代码来做更多的检查,或者当某些数据改变时你必须让更多的代码失效(要不就像C那样,一旦出点儿错就是一身翔,满腚伤!)。如果在语言级别支持这样的改变,那你别无选择地只能默默接受了,优化也就只能帮你到这儿了。

这可预知的需求在类型的成员函数表(成员函数应该保持不变)以及类型的对象布局(可预知的对象布局)上都存在。许多动态类型语言需要他们的虚拟机支持动态的类型布局和对象布局,由此导致虚拟机也无法对代码优化做出有用的假设。投机性的预测(根据已知的类型分配伪类型或是根据之前的对象创建新对象)一样必须得包含代码逻辑来处理类型或对象的改变,以防万一。再次声明,这种情况下优化潜力非常有限,因为VM必须时刻防备着这执行环境说不定冷不丁就变了。

换句话来说,1和2表明:类型不一定非得是静态声明的,但它们需要是静态定义的。大多数的动态语言这两样都不占,但它们真应该做到静态定义类型。

#3 处理器,不可欺

别管你的代码,语言,虚拟机或是JIT写得有多么机智,跑得快不快最终还是得看现代处理器怎么处理你的代码。你需要满足一个长长的期望清单才能从一个系统中获得极致的性能,无视这些指示总是会带来性能上的惩罚。这是终极理论,就好比是最下面那个驮起整个世界的海龟。最终你必须取悦CPU来获取最好的性能。在所有离此甚远的错误考虑中,以及任何不满足期望的性能表现里,你最终肯定能发现是有人试图欺骗CPU。

传统上,静态类型是保证生成良好CPU指令的最好方式。它为我们所苦思冥想的世界描绘了一副清晰的图像,揭示出宇宙的秘密并最终生成了最快的代码。但这样就好比管中窥豹,它假定我们可以在程序运行之前做出完全正确的决定,而且除我们的目标指令集之外什么都不会影响我们。然而,在一个真实的世界里,我们总是需要去竞争CPU们那有限的缓存,线程执行核心,内存访问带宽等资源,并且CPU必然受限于基本的物理规则(它只能承受有限的电流,否则就变煎饼铛了)。编程语言和虚拟机的作者如果无视目标系统的种种期望的话,那他就是在冒险。

咱来瞅瞅几个语言及其所在的位置。

编程语言记分板

Java是静态类型语言并且它的类型是固定的。由于类型结构是可以预知的,所以这是一个颇为理想的情况。无论何时何地,一支玫瑰永远是一支玫瑰。给定合适的动态优化技术之后,没有任何理由使得Java代码跑得一定比静态类型加静态编译的C/C++更慢,理论上讲,没什么东西阻止Java代码被优化成最优的CPU指令。

Dart是动态类型语言(至少类型不是强制性的而且虚拟机不关心它们),但是类型是固定的。如果程序员能容忍这种固定类型的话,Dart是一个非常不错的动态语言而且它仍有可能达到像静态类型的Java和静态编译的C/C++一样的优化水准。

Groovy是动态类型语言,当你静态指定类型时也能进行类型推导和一些优化,但大多数(或全部?)类型并不保证说一定不变。这就导致即使当你静态地指定了类型,一样要生成一些保护性的代码来检查那些类型是不是发生了改变。然而Groovy确实保证说对象在生命期内是一致的,这就避免了运行时对象形状改变的开销。

Ruby和JavaScript是动态类型语言而且类型和对象都可以在运行时改变。这简直是集成了所有超难优化的语言特性。对这俩货,我们顶多做到尝试预测最通用的类型或对象布局,并且插入代码来处理预测出错的情况,但是不可能达到像拥有完全可预知的类型和对象布局的系统一样的性能。不服来战。

当然,当我说“不可能”的时候,我的意思是不可能有通用的方法。特定的情况下的封闭的应用程序也确实可以优化到像拥有静态类型和对象布局系统一样的优化水平。我在我的RubyFlux编译器里就沿着这个方向做过一些工作,在这些工作里我对读到的Ruby代码做了静态分析并假定分析到的成员函数或成员变量就是需要考虑的全部成员了。但是这意味着忽略了所有可以导致类型或对象布局发生变化的语言特性,或者得找到一个方法确定哪些类型或对象布局可能会被这些特性所改变。这真是需要很聪明的编译器才行呀。

Python拥有跟Ruby类似的结构复杂度,但其代码可见的调用栈更增加了额外的复杂度(译者注:sys._getframe())。在这种情况下,栈上的状态都是不可靠的;一个虚拟机甚至不能对手上的值以及给定的调用过程的行为做出任何保证。PyPy通过改写正在执行的代码使得在栈上状态被访问时将其自动提升至堆中的方式来解决这个问题,这是值得尊敬的工作,但是这个技术一方面使得没用的局部变量无法被自动消除(因为你无法预测是否有谁可能会想看到它),另一方面这技术无法在并行执行的环境下正确工作(因为你不能改写其它线程可能正在运行的代码)。再次说明,一个很酷的特性在带来更易用的“动态性”的同时,往往也带来了其中固有的虽可降低但难消除的额外开销。

说人话,好不好!

话说我整这么多是想说什么呢?今晚我瞅着一个比较Dart虚拟机和JVM的在同一个评测标准下的评测文章。评测结果实际上没什么意思……把Dart行对行地翻译到Java之后,Java跑得稍稍慢过Dart。对Java代码做了点儿优化之后,这货变得稍稍领先了。对Dart代码做了些优化之后,Dart又反超Java了。但是这些结果基本上没有任何意义,因为Dart和Java都可以利用类型和对象布局的不变性来进行优化,并最终达到基本相同的优化效果。而在真正重要的方面,它俩是如此地相似以至于虚拟机根本不必关心它们的不同之处。

那对于我个人比较喜欢的语言又怎样呢,比如Ruby?我觉得我们必须承认不管虚拟机技术如何进步,Ruby都不太可能会达到像Dart或Java一样类型固定的语言所能达到的原始性能。不过我们可以接近它们的性能,JRuby在invokedynamic的帮助下,就几乎可以达到像Java一样的函数调用性能,并且通过生成类型信息,我们可以使得Ruby对象的状态几乎和Java的类型一样是可预知的,但我们永远也无法抵达终点。不管底下的虚拟机有多么强悍,如果你不按虚拟机的规矩来,你简直是在顶着风往前走。Ruby on Dart不一定会比Ruby on JVM快多少,因为你一样需要按相似的方式来实现可变的类型和可动态增长的对象。Ruby on PyPy可能会好点,因为这个虚拟机是专门为可变类型和可动态增长的对象而设计的,不过你可能必须要牺牲并行性或是必须接受操作对象的性能不会像Java或Dart那样好。相反地,提供类型不变性保证的语言可能会因为类似的原因在动态语言的虚拟机上提供比动态语言更好的性能(Dart2js),就像它们在它们自己的虚拟机上快的原因一样:它们提供了一个更具一致性的世界的景象,不会给虚拟机任何可能会影响优化的“惊喜”。你是牺牲语言级别的“动态性”换来虚拟机级别的可预知性。

真实的结论

我想我的基本认识就是认识到矛盾总是存在于编程语言所能提供的编程技术与广大程序员日益增长的编程需求之间。根本不存在这样神奇的国度,那里任何一门编程语言和其它任何语言一样快,因为根本就无法预测所有的程度将被如何执行(实际上是,一个给定的程序在给定的通用策略之下如何运行)。但这没什么,大部分编程语言依然可以获得相似的性能,并且随着时间地发展,动态类型/对象布局的语言可能会想出些办法来逐步减少这种动态特性……或者他们坦然接受这种内在的限制。最重要的是程序员们要认识到没有什么是白捡的,并且充分理解语言特性的含义以及他们自己程序的设计决策。


0 0
原创粉丝点击