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

Smalltalk字节码优化值得付出努力吗?,smalltalk,pharo,squeak,Smalltalk,Pharo,Squeak,在榨汁机类中考虑以下方法: Juicer >> juiceOf: aString | fruit juice | fruit := self gather: aString. juice := self extractJuiceFrom: fruit. ^juice withoutSeeds 它生成以下字节码 25 self ; 1 26 pushTemp: 0 ; 2 27 send

榨汁机
类中考虑以下方法:

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
现在取消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
最后一个版本保存了7个堆栈操作中的4个,对应于表达性较差且清晰的源代码:

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

我的问题是这些改进是否是一种幻觉。具体地说,Pharo/Squeak中缺少字节码优化是因为它们几乎没有相关性,还是它们被认为是有益的,但尚未得到解决

编辑


使用寄存器+堆栈体系结构的一个有趣的优点[参见Allen Wirfs Brock和Pat Caudill的著作]是,寄存器提供的额外空间使字节码的操作更容易进行优化。当然,正如下面的答案所指出的,尽管这些优化不如方法内联或多态内联缓存那么重要,但它们不应该被忽略,尤其是当它们与JIT编译器实现的其他优化组合在一起时。另一个需要分析的有趣主题是破坏性优化(即,需要反优化以支持调试器的优化)是否确实必要,或者可以通过非破坏性技术获得足够的性能提升。

当您开始使用此类优化时,最主要的烦恼是调试器接口

从历史上看,调试器一直在模拟字节码级别,需要将字节码映射到相应的Smalltalk指令

因此,我认为增益太低,无法证明复杂化,甚至调试设施的退化

Pharo希望将调试器更改为在更高的级别(抽象语法树)上运行,但我不知道它们将如何以字节码结束,而字节码是VM所知道的全部

在我看来,这种优化最好在JIT编译器中实现,该编译器将字节码转换为机器本机代码

编辑

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

有趣的是,这种优化可以在Smalltalk映像中进行,但只能在VM检测到的热点上进行,就像SISTA的工作一样。例如,见

因此,从SISTA的角度来看,答案是:有趣,尚未解决,但正在积极研究(并在进行中)


据我所知,当方法需要调试时,所有的去优化机制仍然是难点之一。

我认为一个更广泛的问题值得回答:字节码值得付出努力吗?字节码被认为是接近目标机器的代码的紧凑和可移植的表示。因此,它们很容易解释,但执行起来很慢

字节码在这些游戏中都不出色,如果你想编写一个解释器或一个快速的虚拟机,那么字节码通常不是最好的选择。一方面,AST节点更容易解释(只有少数节点类型而不是许多不同的字节码)。另一方面,随着JIT编译器的出现,运行本机代码不仅是可能的,而且速度更快

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

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

再说一次,如果你对可移植代码感兴趣,没有什么比AST更可移植的了。此外,实现基于AST的调试器和分析器比实现基于字节码的调试器和分析器更容易、更实用。剩下的唯一问题是紧凑性,但在任何情况下都可以实现类似ast代码(编码ast,类似于字节码,但表示树)的东西

另一方面,如果您想要全速,那么您将使用具有良好IR和无字节码的JIT。我认为字节码并没有填补当今虚拟机中的许多空白,但仍然主要是为了向后兼容(还有许多直接执行Java字节码的硬件架构示例)


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

我同意,这种改进可能无法证明实现优化所需的复杂性。但是请注意,在调试时,您仍然可以将存储保存在temp中,而在解释时只需跳过它们。关于抖动,您也提出了一个很好的观点。但是,jitter允许进行其他优化,并且在同一位置实现所有优化可能有点不方便。非常感谢您的回复。@LeandroCaniglia在优化更昂贵的发送时,增益开始变得有趣起来
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
25 self                     ; 1
26 self                     ; 2
27 pushTemp: 0              ; 3
28 send: gather:
29 send: extractJuiceFrom:
30 send: withoutSeeds
31 returnTop
Juicer >> juiceOf: aString
    ^(self extractJuiceFrom: (self gather: aString)) withoutSeeds