【问题标题】:Is the &method(:method_name) idiom bad for performance in Ruby?&method(:method_name) 习语对 Ruby 的性能不利吗?
【发布时间】:2011-08-07 23:53:42
【问题描述】:

我最近遇到了&method(:method_name) 语法。 (这里使用Object#method方法-RDoc link)例如,

[5, 7, 8, 1].each(&method(:puts))

相当于

[5, 7, 8, 1].each{|number| puts number}

在 Ruby 的各种实现中,与前者相比,后者是否存在性能损失?如果是,实施者是否正在努力提高其性能?

【问题讨论】:

    标签: ruby performance


    【解决方案1】:

    是的,这似乎对性能不利。

    def time
      start = Time.now
      yield
      "%.6f" % (Time.now - start)
    end
    
    def do_nothing(arg)
    end
    
    
    RUBY_VERSION # => "1.9.2"
    
    # small
    ary = *1..10
    time { ary.each(&method(:do_nothing)) }     # => "0.000019"
    time { ary.each { |arg| do_nothing arg } }  # => "0.000003"
    
    
    # large
    ary = *1..10_000
    time { ary.each(&method(:do_nothing)) }     # => "0.002787"
    time { ary.each { |arg| do_nothing arg } }  # => "0.001810"
    
    
    # huge
    ary = *1..10_000_000
    time { ary.each(&method(:do_nothing)) }     # => "37.901283"
    time { ary.each { |arg| do_nothing arg } }  # => "1.754063"
    

    JRuby 中似乎解决了这个问题:

    $ rvm use jruby
    Using /Users/joshuajcheek/.rvm/gems/jruby-1.6.3
    
    $ xmpfilter f.rb 
    def time
      start = Time.now
      yield
      "%.6f" % (Time.now - start)
    end
    
    def do_nothing(arg)
    end
    
    
    RUBY_VERSION # => "1.8.7"
    
    # small
    ary = *1..10
    time { ary.each(&method(:do_nothing)) }     # => "0.009000"
    time { ary.each { |arg| do_nothing arg } }  # => "0.001000"
    
    
    # large
    ary = *1..10_000
    time { ary.each(&method(:do_nothing)) }     # => "0.043000"
    time { ary.each { |arg| do_nothing arg } }  # => "0.055000"
    
    
    # huge
    ary = *1..10_000_000
    time { ary.each(&method(:do_nothing)) }     # => "0.427000"
    time { ary.each { |arg| do_nothing arg } }  # => "0.634000"
    

    【讨论】:

    • 您的&:method(:do_nothing)do_nothing arg 不同
    • 不确定你的意思。
    • 可能do_nothing 方法在第一个中没有接收参数,但在第二个中接收arg 作为参数
    【解决方案2】:

    由于 Rubinius 是最先进和最积极优化的 Ruby 实现,我在 Rubinius mailinglistasked this questionhere's what Evan Phoenix had to say

    我很遗憾地说,您认为它可能与块相同的假设是完全错误的。有理由你看不到Method#to_proc,这样的分析是2倍:

    1. 大多数(全部?)MRI 分析器不显示 MRI 在 C 中定义的方法,因此它们永远不会出现。
    2. 激活已转换为Proc 的方法的机制全部在C 中,因此调用端的开销也是不可见的。

    你关于艺术差异的观点是正确的。此外,您认为 VM 可以轻松地将其优化为块的想法是完全错误的。 Object#method 不会被检测和优化掉。此外,即使使用运行时优化,仍然需要像逃逸分析这样的东西,因为 #method 返回一个 Method 对象,您必须在其中查看并从中提取信息。在调用方面,被调用的方法只能在块内联的情况下对块做一些特殊的事情,这是只有 Rubinius 有的优化。

    所以要回答你的问题:

    1. Rubinius 是否优化此代码?不,可以吗?是的,但这并不容易。
    2. 及时,是的。
    3. 应该及时,是的。

    注意:他在最后一段中提到的问题是:

    1. Rubinius 目前是否优化了这种无点代码?
    2. 如果没有,可以吗?
    3. 如果可以,应该吗?

    【讨论】:

    • 我已经看到 Symbol#to_proc 出现在 MRI 1.8 的 ruby​​-prof 分析中(没有名称的实现?),所以我不明白为什么 Method#to_proc 不会出现。但是,这是将符号或方法转换为某种 proc 的开销。我怀疑上述 proc 的执行速度可能与普通块的执行速度不同:请参阅来自 stackoverflow.com/questions/6501030/… 的链接
    • 关于 C 中定义的分析方法:我怀疑 perftools 的非 Ruby 版本可能能够做到这一点(即“分析 Ruby VM 和 C 扩展”)。
    • 只是想知道最后两个问题的答案是否改变了?
    【解决方案3】:

    在最新的 ruby​​ 1.9.2 上,它们看起来非常相似/相同

    # Using ruby 1.9.2-p290
    
    require 'benchmark'
    
    Benchmark.measure do
      1000.times { [5, 7, 8, 1].each(&method(:puts)) }
    end
    
    # =>   0.020000   0.020000   0.040000 (  0.066408)
    # =>   0.020000   0.010000   0.030000 (  0.075474)
    # =>   0.020000   0.020000   0.040000 (  0.048462)
    
    Benchmark.measure do
      1000.times { [5, 7, 8, 1].each{|number| puts number} }
    end
    
    # =>   0.020000   0.020000   0.040000 (  0.071505)
    # =>   0.020000   0.020000   0.040000 (  0.062571)
    # =>   0.010000   0.020000   0.030000 (  0.040944)
    

    【讨论】:

      【解决方案4】:

      这是一篇不错的文章(及时):

      http://www.potstuck.com/2011/08/06/ruby-symbols-instead-of-blocks/

      如果您仔细查看马里奥回答中的分析数字,则会因调用 Symbol#to_proc 而导致额外的方法调用受到轻微惩罚。

      只是一个猜测,但我会说不,他们可能不会很快加快速度。

      【讨论】:

      • 链接描述的是&:foo(调用object.foo),而不是&method(:foo)(调用foo(object))。
      • 很抱歉。显然他们不是同一个电话。不过还是一篇不错的文章!
      【解决方案5】:

      从 ruby​​ 1.9.3-p327 开始

      def time &block
        start = Time.now
        yield
        puts "%s : %.6f" % block.to_s, (Time.now - start))
      end
      
      RUBY_VERSION # => "1.9.3-p327"
      
      # small
      ary = *1..10
      time { ary.each(&:to_i) }     # => "0.000010"
      time { ary.each { |arg| arg.to_i } }  # => "0.000002"
      
      # large
      ary = *1..10_000
      time { ary.each(&:to_i) }     # => "0.000494"
      time { ary.each { |arg| arg.to_i } }  # => "0.000798"
      
      # huge
      ary = *1..10_000_000
      time { ary.each(&:to_i) }     # => "0.504329"
      time { ary.each { |arg| arg.to_i } }  # => "0.883390"
      

      【讨论】:

      • 这有点不同,因为它调用的是绑定到对象的方法。
      猜你喜欢
      • 1970-01-01
      • 2020-10-26
      • 2018-03-25
      • 2010-11-04
      • 2022-10-19
      • 1970-01-01
      • 1970-01-01
      • 2014-09-25
      • 2012-02-28
      相关资源
      最近更新 更多