平均而言,我可以合理地期望perform:
比发送文字消息慢多少?我是否应该避免perform:
在循环中发送,类似于对 Perl/Python 程序员的警告,以避免在循环中调用eval("...")
(Compiler evaluate:
在 Smalltalk 中)?
我主要关心 Squeak,但也对其他 Smalltalks 感兴趣。此外,perform:with:
变体的开销是否更大?谢谢
#perform:
不像eval()
。(无论如何,在性能方面)的问题eval()
在于它必须编译您在运行时发送的代码,这是一个非常缓慢的操作。#perform:
另一方面,Smalltalk等价于 Rubysend()
或 Objective-C performSelector:
(事实上,这两种语言都深受 Smalltalk 的启发)。像这样的语言已经根据它们的名称查找方法——#perform:
只允许您在运行时而不是在写入时指定名称。它不必解析任何语法或编译任何类似eval()
.
它会慢一点(至少一个额外的方法调用的成本),但它不像eval()
. 此外,具有更多参数的变体不应该显示速度与简单的任何差异perform:whatever
。我不能专门谈论关于 Squeak 的太多经验,但这就是它通常的工作方式。
这是我机器上的一些数字(它是 Smalltalk/X,但我想这些数字是可比的——至少比率应该是):
被调用的方法 "foo" 和 "foo:" 是一个 noops(即由一个 ^self 组成):
self foo ... 3.2 ns
self perform:#foo ... 3.3 ns
[self foo] value ... 12.5 ns (2 sends and 2 contexts)
[ ] value ... 3.1 ns (empty block)
Compiler valuate:('TestClass foo') ... 1.15 ms
self foo:123 ... 3.3 ns
self perform:#foo: with:123 ... 3.6 ns
[self foo:123] value ... 15 ns (2 sends and 2 contexts)
[self foo:arg] value:123 ... 23 ns (2 sends and 2 contexts)
Compiler valuate:('TestClass foo:123') ... 1.16 ms
注意“执行:”和“评估:”之间的巨大区别;评估是调用编译器来解析字符串,生成一个丢弃的方法(字节码),执行它(它在第一次调用时被jited)并最终被丢弃。编译器实际上主要用于 IDE 并从外部流中归档代码;它具有用于错误报告、警告消息等的代码。通常,当性能至关重要时,eval 不是您想要的。
来自戴尔 Vostro 的计时;您的里程可能会有所不同,但比率不会。我试图通过测量空循环时间并减去来获得净执行时间;此外,我运行了 10 次测试并利用了最好的时间,以消除操作系统/网络/磁盘/电子邮件或任何干扰。但是,我并不真正关心无负载机器。测量代码是(用上面的东西替换了第二次重复参数):
callFoo2
|t1 t2|
t1 :=
TimeDuration toRun:[
100000000 timesRepeat:[]
].
t2 :=
TimeDuration toRun:[
100000000 timesRepeat:[self foo:123]
].
Transcript showCR:t2-t1
编辑: PS:我忘了提:这些是 IDE 内部的时间(即字节码抖动执行)。由于更好的寄存器分配算法,静态编译的代码(使用 stc 编译器)在这些低级微基准测试中通常会更快一些(20-30%)。
编辑:前几天我试图重现这些数字,但得到了完全不同的结果(简单调用为 8ns,而执行为 9ns)。所以要非常小心这些微定时,因为它们完全耗尽了一级缓存(空消息甚至会忽略上下文设置,或者被内联)——它们通常不能很好地代表整体性能。