考虑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编译器实现的其他方法结合使用时。另一个值得分析的有趣话题是破坏性优化(即,需要去优化以支持调试器的那个)是否真的是必要的,或者非破坏性>可以获得足够的性能提升技术。
答案 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架构之外,还是在最后一次转换中是否有技术上的好处,或者在字节码级别实际上是否有任何优化。