在Smalltalk中,在运行时查找方法可能涉及大量步骤,因为子类的方法字典在其超类中不包含方法,并且需要指针追踪来查找方法。优化将是每个子类将所有超类方法存储在其方法字典中 问题:如何做到这一点?
一个明显的缺点是空间成本,但我只是想知道如何在Smalltalk中完成这项工作?这与为最近调用的方法创建单独的缓存不同。
答案 0 :(得分:5)
有很多方法可以缓存查找结果。只需阅读一些基本的VMimplementation论文 - 从绿皮书开始。你甚至可以在网上免费找到t - http://stephane.ducasse.free.fr/FreeBooks.html并查看“历史的位数,建议的话”的底部。 普通解释器可以使用简单的散列键控缓存。翻译虚拟机可以进入内联缓存,多态内联缓存,自适应缓存......不需要像C ++ vtables一样痛苦,这几乎就是你所建议的。 我们已经覆盖了几十年。它已经解决了。
答案 1 :(得分:4)
在Smalltalk中,在运行时查找方法可能涉及大量步骤,因为子类的方法字典在其超类中不包含方法
对于不尝试进行任何优化的解释Smalltalk也是如此,许多Smalltalks(例如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的方法都会做某事(应该如此),你基本上至少要调用两次这个行为。
答案 2 :(得分:2)
有些方言使用另一种方法来最小化枚举MethodDictionaries
链所需的操作次数:所谓的MDA
或方法字典数组。
这个想法是让类在第一个插槽中保留一个包含其{1}}实例的数组,然后保留其超类的MD
,依此类推。
这种技术的一个好处是它可以在实例级别实现。可以存储类的MD
,而不是将类存储在对象头中。这与方法发送中发生的类检查完全兼容,但它变为MDA
- 而是检查。
此外,如果您重新启用MDA
,则可以向其添加行为,以使其支持嵌套。这样,外部数组可能包含MDA
或嵌套MDs
。嵌套可用于添加特定于实例的行为,而无需从类的MDA
中删除:将特定于实例的行为放在MDA
中并将其存储在插槽1中,然后放入{{1}插槽2中的类。如果类的MD
发生更改,则实例不必执行任何特殊操作即可附加到已修改的MDA
。 (如果实例在插槽1中创建了自己的MDA
及其MDA
,在后续插槽中创建了类MDA
,则该类的MD
将与实例分离一个)