【问题标题】:Are Smalltalk bytecode optimizations worth the effort?Smalltalk 字节码优化值得付出努力吗?
【发布时间】:2015-03-18 15:10:24
【问题描述】:

考虑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。它们在 gather 的结果下方插入 self。通过在发送第一条消息之前推送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

最后一个版本,它节省了 7 个堆栈操作中的 4 个,对应于表达力和清晰的源代码:

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

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

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

编辑

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

【问题讨论】:

    标签: smalltalk pharo squeak


    【解决方案1】:

    当您开始使用此类优化时,主要的烦恼是调试器界面。

    从历史上看,目前仍在 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,答案是:有趣,尚未解决,但正在积极研究(并且正在进行中)!

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

    【讨论】:

    • 我同意,收益可能无法证明实现优化所需的复杂性。但是请注意,您仍然可以在调试时将存储保存在临时文件中,并在解释时跳过它们。您还对抖动提出了很好的观点。但是,抖动承认其他优化,并且在同一个地方实现所有这些优化可能有点不方便。非常感谢您的回复。
    • @LeandroCaniglia 当还优化比堆栈操作更昂贵的发送时,增益开始变得有趣 - 请参阅我的编辑
    • @LeandroCaniglia 关于调试,想象一下我执行了优化的方法,但是由于#extractJuiceFrom: 正在将#press 发送到水果并且不理解press,因此发生了未处理的异常。如何显示 temps 水果和果汁的值并不是那么明显,如果我将其中一个 temps 分配给另一个对象等会发生什么......
    • 你是对的。 (即使果汁不会成为问题,因为如果没有媒体,它就不会存在;-)
    【解决方案2】:

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

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

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

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

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

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

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

    【讨论】:

    • 这是一个非常有趣的话题。在我正在进行的一个项目中,我们采用了一种中间方法,通过该方法我们具体化了字节码(使用 PetitParser。)我们编译的方法仍然有字节码(字节),但我们的开发工具和 Jitter 使用它们的具体化版本。此外,Smalltalk 编译器会发出具体的字节码,这些字节码稍后会将自己转换为字节。
    • 本人从事编译器和抖动业务,我只能确认这一点。抖动实际上必须分析字节码并重建大量因字节码编码而丢失的信息(循环、公共子表达式、常量传播等),以生成更好的代码。
    • 我希望看到一些参考资料来支持这个答案。字节码不会像 AST 那样破坏缓存,就所有意图和目的而言,它与遍历节点链表一样有效。没有证据表明 AST 比字节码更便携,正如 Smalltalk、Tripos 的原始实现(使用 BCPL 编译为“O-Code”)和 UCSD P-System 所证明的那样。所有可用于生产的 VM 都使用字节码或其等价物(即(内)直接线程代码表示,通常用于较小架构的编译器中),这很能说明问题。
    • @SamuelA.FalvoII,你所说的可移植性是 100% 正确的,我只是没有花时间更好地描述我的意思,这与易于实施有关。我的经验是实现 AST 解释器比字节码解释器容易得多。关于缓存粉碎,这无关紧要,因为字节码被第三层(V8、SpiderMonkey、JavaScriptCore、HotSpot、Graal)中的内部 IR 所取代。字节码只用于从不经常执行的代码,所以我认为它们不值得优化。
    • 为了给主题添加更多内容,目前在 IR 设计中似乎存在更多线性 IR 或基于图形的 IR(节点海)之间的紧张关系。虽然前者有更快的字节码到本机代码的时间(而且对我来说更丑),但后者似乎找到了更多的优化,因为它们统一了控制流和数据流。这有点像 ast 与字节码,但级别要低得多。
    猜你喜欢
    • 1970-01-01
    • 2018-04-20
    • 1970-01-01
    • 2010-11-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-21
    相关资源
    最近更新 更多