为什么某些块关闭优化是好的和有效的?
Why certain block closure optimization is good and valid?
在 2001 年的一篇非常有趣的 post 中,Allen Wirfs-Brock 解释了如何在不具体化(本机)堆栈的情况下实现块闭包。
从他提出的许多想法中,有一个我不太明白,我认为在这里问是个好主意。他说:
任何在块的生命周期内永远无法分配的变量(例如,封闭方法和块的参数)不需要放在环境中,如果变量的副本放在创建时的闭包
有两件事我不确定我是否理解得足够好:
- 为什么使用只读变量的两个副本比将变量移动到环境中更快?是因为封闭上下文访问堆栈中的(原始)变量会更快吗?
- 如何保证两个变量保持同步?
问题1肯定另有原因。否则我看不到收益(与实施优化的成本相比。)
对于问题 2,采用在方法中而非块中分配的非参数。为什么存储在堆栈中的 oop 在块的生命周期中会保持不变?
我想我知道问题 2 的答案:因为块的执行不能与方法的执行交织在一起,即,当块存在时,封闭上下文不会 运行。但是有没有什么办法可以在块处于活动状态时临时修改堆栈?
感谢@aka.nice 的评论,我在 Clement Bera 的 post 中找到了这两个问题的答案,其阅读既愉快又清晰。
对于Q1,我们先说Allen 的评论意味着只读变量的副本可以放在块的堆栈中,就好像它是块的局部临时变量一样。这样做的好处只有在 all 在块外定义并在块内使用的变量从未写入块中时才会实现。在这些情况下,不需要创建环境数组并发出任何序言或结尾来处理它。
访问堆栈变量的机器码等同于访问环境所需的机器码,因为第一个将使用 [ebp + offset]
寻址位置,而第二个将使用 [edi + offest]
,一次 edi
已设置为指向环境数组(Clement 表示法中的 tempVector
。)因此,如果某些但不是所有环境变量都是只读的,则没有任何好处。
第二个问题在Clement的优秀博客中也有解答。是的,还有另一种方法可以打破原始变量与其在块堆栈中的副本之间的同步:调试器(正如 aka.nice 告诉我们的那样!)如果程序员修改封闭上下文中的变量,调试器将需要检测动作并更新副本。如果程序员修改块堆栈中保存的副本,则相同。
很高兴我决定 post 这里的问题。我从 aka.nice 和 Clement Bera 那里得到的帮助,加上一些人通过电子邮件给我的评论,对我的理解有很大帮助。
最后一点。 Wirfs-Brock 声称避免方法上下文的具体化是强制性的。我倾向于同意。但是,如果具体化遵循轻量级模式,则可以更好地实现对这些数据结构的许多重要操作。更准确地说,在调试时,您可以使用指向本机堆栈的 "viewers" 对这些上下文进行建模,并使用两个索引来分隔与所分析的激活相对应的部分。这既高效又干净,这两种技术的结合带来了世界上最好的,因为你可以同时拥有速度和表现力。 Smalltalk 太棒了。
在 2001 年的一篇非常有趣的 post 中,Allen Wirfs-Brock 解释了如何在不具体化(本机)堆栈的情况下实现块闭包。
从他提出的许多想法中,有一个我不太明白,我认为在这里问是个好主意。他说:
任何在块的生命周期内永远无法分配的变量(例如,封闭方法和块的参数)不需要放在环境中,如果变量的副本放在创建时的闭包
有两件事我不确定我是否理解得足够好:
- 为什么使用只读变量的两个副本比将变量移动到环境中更快?是因为封闭上下文访问堆栈中的(原始)变量会更快吗?
- 如何保证两个变量保持同步?
问题1肯定另有原因。否则我看不到收益(与实施优化的成本相比。)
对于问题 2,采用在方法中而非块中分配的非参数。为什么存储在堆栈中的 oop 在块的生命周期中会保持不变?
我想我知道问题 2 的答案:因为块的执行不能与方法的执行交织在一起,即,当块存在时,封闭上下文不会 运行。但是有没有什么办法可以在块处于活动状态时临时修改堆栈?
感谢@aka.nice 的评论,我在 Clement Bera 的 post 中找到了这两个问题的答案,其阅读既愉快又清晰。
对于Q1,我们先说Allen 的评论意味着只读变量的副本可以放在块的堆栈中,就好像它是块的局部临时变量一样。这样做的好处只有在 all 在块外定义并在块内使用的变量从未写入块中时才会实现。在这些情况下,不需要创建环境数组并发出任何序言或结尾来处理它。
访问堆栈变量的机器码等同于访问环境所需的机器码,因为第一个将使用 [ebp + offset]
寻址位置,而第二个将使用 [edi + offest]
,一次 edi
已设置为指向环境数组(Clement 表示法中的 tempVector
。)因此,如果某些但不是所有环境变量都是只读的,则没有任何好处。
第二个问题在Clement的优秀博客中也有解答。是的,还有另一种方法可以打破原始变量与其在块堆栈中的副本之间的同步:调试器(正如 aka.nice 告诉我们的那样!)如果程序员修改封闭上下文中的变量,调试器将需要检测动作并更新副本。如果程序员修改块堆栈中保存的副本,则相同。
很高兴我决定 post 这里的问题。我从 aka.nice 和 Clement Bera 那里得到的帮助,加上一些人通过电子邮件给我的评论,对我的理解有很大帮助。
最后一点。 Wirfs-Brock 声称避免方法上下文的具体化是强制性的。我倾向于同意。但是,如果具体化遵循轻量级模式,则可以更好地实现对这些数据结构的许多重要操作。更准确地说,在调试时,您可以使用指向本机堆栈的 "viewers" 对这些上下文进行建模,并使用两个索引来分隔与所分析的激活相对应的部分。这既高效又干净,这两种技术的结合带来了世界上最好的,因为你可以同时拥有速度和表现力。 Smalltalk 太棒了。