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

Posted

技术标签:

【中文标题】proc、Proc.new、lambda 和 stabby lambda 之间的速度差异【英文标题】:Speed differences between proc, Proc.new, lambda, and stabby lambda 【发布时间】:2014-04-20 01:46:01 【问题描述】:

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 = ->xx*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).eachblock.call
end

test2Proc.new|x| x*x
test2proc|x| x*x
test2lambda|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 的开销。

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

为什么普通方法调用比 procs 和 lambda 快得多(减少 1/3 时间)? 人们是否认为这可能会随着不同的块功能等而改变? 在不同方法之间进行选择是否还有其他(基于性能的)原因?

【问题讨论】:

没有没有个基于性能的理由来选择它们。它们是不同的东西,为不同的目的而构建。试图比较它们的性能就像比较赛车和拖车的速度。 顺便说一句,你可能想知道ruby-doc.org/stdlib-2.1.0/libdoc/benchmark/rdoc/Benchmark.html :] @LoganSerman 谢谢,很高兴知道。 @meagar 我会提出异议,如果我不将我的 proc 或 lambda 放入一个方法中并且从不调用 return 或 break (这对我来说很常见,因为我尝试编写了许多我的功能风格的脚本并使用我们拥有的美妙的新惰性迭代器)然后它们在功能上是等效的,(不是吗?)在这种情况下我应该用什么来选择它们,我目前使用 stabby lambda 因为它是最快的打字,但我想做出明智的决定。在我刚才提出的情况下,它们有什么不同吗? 【参考方案1】:

看来您有三个问题。中间一个我不清楚,所以我将解决其他两个:

为什么普通方法调用这么快?

这是最简单的问题。

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

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

另外,请记住 Ruby 支持闭包,我敢打赌在决定间接代码应该在哪个环境中运行时会有一些开销。

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

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

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

在不同方法之间进行选择是否还有其他(基于性能的)原因?

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

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

直接调用更快一点也不奇怪。

这种事情产生影响的地方是经常调用的代码,其中开销加起来以挂钟的方式很明显。在这种情况下,进行各种丑陋的优化以试图提高速度是有意义的,一直到内联代码(完全避开函数调用开销)。

例如,假设您的应用程序使用数据库框架。您对其进行分析并找到一个经常调用的小型(例如,小于 20 次乘法工作)的函数,该函数将数据从数据库结果复制到数据结构中。这样的函数可能包含大部分结果处理,并且简单地内联该函数可能会以牺牲一些代码清晰度为代价来节省大量的 CPU 时间。

只需将您的“平方函数”内联到具有十亿步的长数值计算中,就可以为您节省大量时间,因为该操作本身比直接调用方法所花费的时间要少得多。

不过,在大多数情况下,最好使用干净、清晰的代码。

【讨论】:

感谢您的回答。碰巧你回答了我所有的三个问题。为了澄清第二个问题是询问函数的主体是否会改变不同函数调用语法的相对速度,答案似乎是,不,不是相对速度,只是调用的绝对速度(似乎暗示根据你的观点)。 啊,我明白了。好吧,很高兴为您提供帮助。 所以关键是,如果计算量很大并且要调用一次,我们可以内联它们。但如果它很简单,最好通过方法调用。我说的对吗?

以上是关于proc、Proc.new、lambda 和 stabby lambda 之间的速度差异的主要内容,如果未能解决你的问题,请参考以下文章

传递给`instance_exec`时如何执行proc

Ruby:如何获取可选 proc 参数的默认值

我可以对传递给方法的块强制执行 arity 吗?

为啥显式返回会对 Proc 产生影响?

为啥我不能在 Ruby 中将块传递给 proc?

块(下)