Smalltalk字节码优化值得付出努力吗?

时间:2015-01-20 11:43:48

标签: smalltalk pharo squeak

考虑Juicer类中的以下方法:

Juicer >> juiceOf: aString
    | fruit juice |
    fruit := self gather: aString.
    juice := self extractJuiceFrom: fruit.
    ^juice withoutSeeds

它生成以下字节码

25 self                     ; 1
26 pushTemp: 0              ; 2
27 send: gather:
28 popIntoTemp: 1           ; 3
29 self                     ; 4
30 pushTemp: 1              ; 5
31 send: extractJuiceFrom:
32 popIntoTemp: 2           ; 6 <-
33 pushTemp: 2              ; 7 <-
34 send: withoutSeeds
35 returnTop

现在注意32和33取消:

25 self                     ; 1
26 pushTemp: 0              ; 2
27 send: gather:
28 popIntoTemp: 1           ; 3 *
29 self                     ; 4 *
30 pushTemp: 1              ; 5 *
31 send: extractJuiceFrom:
32 storeIntoTemp: 2         ; 6 <-
33 send: withoutSeeds
34 returnTop

接下来考虑28,29和30.他们在self的结果下面插入gather。在发送第一条消息之前推送self可以实现相同的堆栈配置:

25 self                     ; 1 <-
26 self                     ; 2
27 pushTemp: 0              ; 3
28 send: gather:
29 popIntoTemp: 1           ; 4 <-
30 pushTemp: 1              ; 5 <-
31 send: extractJuiceFrom:
32 storeIntoTemp: 2         ; 6
33 send: withoutSeeds
34 returnTop

现在取消29和30

25 self                     ; 1
26 self                     ; 2
27 pushTemp: 0              ; 3
28 send: gather:
29 storeIntoTemp: 1         ; 4 <-
30 send: extractJuiceFrom:
31 storeIntoTemp: 2         ; 5
32 send: withoutSeeds
33 returnTop

临时书1和2是书写但未阅读。所以,除了调试时,可以跳过它们,导致:

25 self                     ; 1
26 self                     ; 2
27 pushTemp: 0              ; 3
28 send: gather:
29 send: extractJuiceFrom:
30 send: withoutSeeds
31 returnTop

最后一个版本可以节省4个7个堆栈操作,对应于表达不太清晰的源代码:

Juicer >> juiceOf: aString
    ^(self extractJuiceFrom: (self gather: aString)) withoutSeeds

另请注意,还有其他可能的优化,Pharo(我还没有检查过Squeak)没有实现(例如,跳转链接)。这些优化会鼓励Smalltalk程序员更好地表达他们的意图,而无需支付额外计算的成本。

我的问题是这些改进是否是一种错觉。具体而言,Pharo / Squeak缺少字节码优化,因为它们已知具有很小的相关性,或者它们被视为有益但尚未得到解决?

修改

使用寄存器+堆栈架构的一个有趣的优点[参见Allen Wirfs-Brock和Pat Caudill的A Smalltalk Virtual Machine Architectural Model是寄存器提供的额外空间使得为了优化而更容易操作字节码。当然,即使这些优化与方法内联或多态内联缓存不相关,如下面的答案所指出的,它们也不应被忽视,尤其是当与JIT编译器实现的其他方法结合使用时。另一个值得分析的有趣话题是破坏性优化(即,需要去优化以支持调试器的那个)是否真的是必要的,或者非破坏性可以获得足够的性能提升技术。

2 个答案:

答案 0 :(得分:4)

开始玩这种优化时的主要烦恼是调试器界面。

历史上仍然在Squeak中,调试器正在模拟字节码级别,需要将字节码映射到相应的Smalltalk指令。

所以我认为增益太低,无法证明复杂化,甚至调试设备的恶化程度更低。

Pharo希望将调试器更改为更高级别的操作(抽象语法树),但我不知道它们将如何以字节码结束,这是VM所知道的。

IMO,这种优化最好在JIT编译器中实现,它将字节码转换为机器本机代码。

修改

最大的收获是消除发送本身(通过内联),因为它们比堆栈操作更昂贵(x10) - 每秒执行的字节码比测试1个tinyBenchmarks(COG VM)时发送的字节码多10倍

有趣的是,此类优化可能发生在Smalltalk映像中,但仅限于VM检测到的热点,如SISTA工作中那样。请参阅示例https://clementbera.wordpress.com/2014/01/22/the-sista-chronicles-iii-an-intermediate-representation-for-optimizations/

所以,根据SISTA,答案是:有趣,尚未解决,但积极研究(并且正在进行中)!

当我必须调试方法时,所有用于去优化的机制仍然是我理解的难点之一。

答案 1 :(得分:2)

我认为一个更广泛的问题值得回答:是值得努力的字节码吗?字节码被认为是一种紧凑且可移植的代码表示,它与目标机器相近。因此,它们易于解释,但执行速度慢。

字节码在这些游戏中并不出色,如果您想要编写解释器或快速VM,这通常会使它们不是最佳选择。一方面, AST节点更容易解释(只有少数节点类型与许多不同的字节码相比)。另一方面,随着JIT编译器的出现,很明显运行本机代码不仅可行而且更快

如果你看看JavaScript的最有效的VM实现(可以被认为是当今最现代的编译器)以及Java(HotSpot,Grail),你会发现它们都使用分层编译方案。方法最初是从AST中解释出来的,只有当它们成为热点时才会被触发。

在最难编译的层上没有字节码。编译器中的关键组件是其中间表示,字​​节码不满足所需的属性。最优化的IR更细粒度:它们采用SSA形式,并允许特定的寄存器和内存表示。这样可以进行更好的代码分析和优化。

然后,如果您对便携式代码感兴趣,那么就没有比AST更便携的东西了。此外,与基于字节码的调试器和分析器相比,实现基于AST的调试器和分析器更容易,更实用。唯一剩下的问题是紧凑性,但无论如何你可以实现像ast代码(编码的asts,类似于字节码但代表树)

另一方面,如果你想要全速,那么你将选择具有良好IR和无字节码的JIT。我认为字节码不会填补今天VM中的许多空白,但仍然主要是为了向后兼容(还有很多硬件架构直接执行Java字节码的例子)。

还有一些与字节码相关的Cog VM的酷实验。但据我所知,他们将字节码转换为另一个IR进行优化,然后将它们转换回字节码。我不确定除了重用原始JIT架构之外,还是在最后一次转换中是否有技术上的好处,或者在字节码级别实际上是否有任何优化。