proc,Proc.new,lambda和stabby lambda之间的速度差异

时间:2014-03-14 16:22:11

标签: ruby performance lambda proc

Procs和lambdas differ关于方法范围以及return关键字的影响。我对它们之间的性能差异很感兴趣。我写了一个测试,如下所示:

def time(&block)
  start = Time.now
  block.call
  p "that took #{Time.now - start}"
end
def test(proc)
  time{(0..10000000).each{|n| proc.call(n)}}
end
def test_block(&block)
  time{(0..10000000).each{|n| block.call(n)}}
end
def method_test
  time{(1..10000000).each{|n| my_method(n)}}
end

proc1 = Proc.new{|x| x*x}
proc2 = proc{|x| x*x}
lam1 = lambda{|x| x*x}
lam2 = ->x{x*x}

def my_method(x)
  x*x
end

test(proc1)
test(proc2)
test(lam1)
test(lam2)
test_block{|x| x*x}
test(method(:my_method))
method_test

此代码的结果如下所示。

"that took 0.988388739"
"that took 0.963193172"
"that took 0.943111226"
"that took 0.950506263"
"that took 0.960760843"
"that took 1.090146951"
"that took 0.644500627"

method(:my_method)是最慢的,这是因为它检查循环中每次迭代的查找表。

同样,另一项测试如下:

def test2(&block)
  time{(0..1000000).each{block.call}}
end

test2{Proc.new{|x| x*x}}
test2{proc{|x| x*x}}
test2{lambda{|x| x*x}}
test2{->(x){x*x}}

返回此结果:

"that took 0.415290453"
"that took 0.378787963"
"that took 0.3888118"
"that took 0.391414639"

Proc.new是最慢的创建方法,这是因为我们有创建整个对象来包装proc的开销。

我断言procs和lambdas的执行时间是相同的,无论它们的创建方法如何。

  • 为什么正常的方法调用比procs和lambdas(1/3时间减少)快得多?
  • 人们是否认为这可能会随着不同的功能块等而改变。
  • 在不同的方法之间是否还有其他(基于表现)的理由?

1 个答案:

答案 0 :(得分:6)

所以看来你有三个问题。中间的一个我不清楚,所以我将解决另外两个:

为什么正常的方法调用要快得多?

这是更容易的问题。

首先要意识到这里涉及的时间是函数调用开销。我根据你的代码做了我自己的计时(但是使用了身份函数而不是乘法),非直接调用的时间延长了49%。通过一次乘法,非直接调用只需要43%的时间。换句话说,你看到很大差异的一个原因是你的功能本身几乎什么也没做。即使是单次乘法也会使差异的6%“消失”。在任何合理复杂度的方法中,方法调用开销通常占总时间的相对较小的百分比。

接下来,请记住,proc / block / lambda本质上是一个被携带的代码块(尽管块文字不能保存到变量中)。这意味着比方法调用更多级别的间接...意味着至少CPU必须遍历指向某个东西的指针。

另外,请记住Ruby支持闭包,我认为在确定间接代码应该运行的环境时会有一些开销。

在我的机器上,运行直接调用函数的C程序比使用指向函数的指针的开销少10%。像Ruby这样的解释性语言,其中也涉及到闭包,肯定会使用更多。

我的测量(Ruby 2.2)表明直接方法调用大约需要6次乘法,间接调用大约需要10次。

因此,虽然开销几乎是其两倍,但请记住,两种情况下的开销通常都相对较小。

在不同的方法之间是否还有其他(基于表现)的理由?

我会说,鉴于上述数据,答案通常是否定的:您最好使用能够提供最易维护代码的构造。

选择其中一个绝对有充分的理由。老实说,我对lambda和块之间的差异感到惊讶(在我的机器上,lambda的开销减少了20%)。 Lambdas创建包含参数列表检查的匿名方法,所以如果有什么我会期望它稍微慢一点,但我的测量结果将它提前了。

直接调用更快,简直就不足为奇了。

这种事情产生影响的地方是非常频繁地称为代码,其中开销在壁挂式的方式中显得更加明显。在这种情况下,做各种丑陋的优化以尝试压缩更快的速度,一直到内联代码(完全避开函数调用开销)是有意义的。

例如,假设您的应用程序使用数据库框架。您对它进行分析并找到一个频繁调用的小型(例如,少于20次乘法运算)函数,该函数将数据从数据库结果复制到数据结构中。这样的函数可能构成了结果处理的最大份额,并简单地内联该函数可能会牺牲一些代码清晰度来节省大量的CPU时间。

简单地将“方形函数”内联到一个包含十亿步的长数值计算中可以节省大量时间,因为操作本身比直接方法调用花费的时间少得多。

但在大多数情况下,使用干净,清晰的代码会更好。