在 Smalltalk 中,在运行时查找方法可能涉及大量步骤,因为子类的方法字典不包含其超类中的方法,并且需要指针追踪来查找方法。对每个子类进行优化以将所有超类方法存储在其方法字典中。
问题:如何做到这一点?
一个明显的缺点是空间成本,但我只是想知道如何在 Smalltalk 中做到这一点?这与为最近调用的方法制作单独的缓存不同。
在 Smalltalk 中,在运行时查找方法可能涉及大量步骤,因为子类的方法字典不包含其超类中的方法,并且需要指针追踪来查找方法。对每个子类进行优化以将所有超类方法存储在其方法字典中。
问题:如何做到这一点?
一个明显的缺点是空间成本,但我只是想知道如何在 Smalltalk 中做到这一点?这与为最近调用的方法制作单独的缓存不同。
有很多方法可以缓存查找结果。只需阅读一些基本的 VMimplementation 论文 - 从绿皮书开始。您甚至可以在网上免费找到 t - http://stephane.ducasse.free.fr/FreeBooks.html并在底部附近查找“Bits of History, Words of Advice”。普通解释器可以使用简单的散列键控缓存。翻译虚拟机可以进入内联缓存、多态内联缓存、自适应缓存......不需要像 C++ vtables 这样痛苦的东西,这几乎就是你所建议的。我们已经涵盖了几十年。解决了。
在 Smalltalk 中,在运行时查找方法可能涉及大量步骤,因为子类的方法字典不包含其超类中的方法
这对于不尝试进行任何优化的解释型 Smalltalk 来说是正确的,许多 Smalltalk(例如 Pharo)实际上是编译的,并且虚拟机中有很多优化......所以消息查找肯定不是我会担心的事情性能方面。
至于所谓的类扁平化……幼稚的方式其实很简单,复制所有的父方法
Child withAllSuperclassesDo: [ :cls |
cls methods do: [ :m |
(Child selectors includes: m selector) ifFalse: [
Child compile: m sourceCode classified: m protocol
]
]
]
果然所有的方法都在那里
您可能不想复制完整的层次结构,但您可以轻松地将其限制为例如一个包或几个祖先。
super
?可能有一个不太好的捕获super
,它依赖于在层次结构中的特定位置。例如,如果您在使用super
...的父级中覆盖某些方法并调用方法,然后将其展平...您可能会陷入无限循环,或者更糟。
因此,如果所有此类引用都正确,您可能需要分析源代码……我不确定这是否可以自动完成,因为它可能取决于代码的逻辑,您必须用自己的眼睛和头脑进行分析. 这也意味着您不能盲目地复制所有方法或从多个类中指向同一个方法,因为super
它们具有不同的含义。
正如阿莫斯恰当地指出的那样……这是不可重构的。
更新以回答@aka.nice 的评论(并进一步说明为什么super
有问题)
压扁前在左边,压平后在右边。
几乎任何时候包含 super 的方法都会做某事(因为它应该),你基本上是在调用该行为至少两次。
一些方言使用另一种技术来最小化枚举链所需的操作数量MethodDictionaries
:所谓的MDA
或方法字典数组。
MD
这个想法是让类在第一个槽中保留一个包含其实例的数组,然后MD
是其超类的,依此类推。
这种技术的一个好处是它可以在实例级别实现。可以存储类的,而不是将类存储在对象头中MDA
。这与方法发送中发生的类检查 100% 兼容,只是它变成了MDA
-check。
此外,如果您具体化,MDA
您可以向其添加行为以使其支持嵌套。这样,外部数组可以包含MDs
或嵌套MDA
. 嵌套可用于在不脱离MDA
类的情况下添加特定于实例的行为:将特定于实例的行为放入 aMD
并将其存储在插槽 1 中,然后将MDA
类MDA
的该实例不必做任何特殊的事情来附加到修改后的MDA
. (如果实例在插槽 1 中创建了自己的实例MDA
,并且在后续插槽中创建了类,则该类将与实例一分离)MD
MDs
MDA
顺便说一句,空间成本很小,因为没有任何重复。