Is the &method(:method_name) idiom bad for performance in Ruby?

前端 未结 5 680
既然无缘
既然无缘 2020-12-03 11:20

I\'ve recently come across the &method(:method_name) syntax. (This uses the Object#method method - RDoc link) For example,

[5,         


        
相关标签:
5条回答
  • 2020-12-03 11:50

    Here is a nice write up on it(just in time):

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

    If you look closely at the profiling numbers in Mario's answer there is a slight penalty for the additional method calls as a result of calling Symbol#to_proc.

    Just a guess, but I would say no, they probably won't be speeding it up anytime soon.

    0 讨论(0)
  • 2020-12-03 11:51

    It would appear they are both very similar/ the same on the latest 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)
    
    0 讨论(0)
  • 2020-12-03 11:53

    as of 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"
    
    0 讨论(0)
  • 2020-12-03 11:55

    Yes, it appears to be bad for performance.

    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"
    

    It looks like this is addressed in 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"
    
    0 讨论(0)
  • 2020-12-03 12:08

    Since Rubinius is the most advanced and most aggressively optimizing Ruby implementation, I asked this question on the Rubinius mailinglist, and here's what Evan Phoenix had to say:

    Your assumption that it could be the same as a block is, I'm sad to say, dead wrong. There reason you don't see Method#to_proc and such in profiling is 2 fold:

    1. Most (all?) MRI profilers do not show methods that MRI defines in C, so they'd never show up.
    2. The mechanism for activating a method that has been turned into a Proc is all in C, so the overhead is invisible on the invocation side too.

    Your point about the arty differences are right on. Additionally, your thinking that a VM could easily optimize it into a block is quite wrong. Object#method is a not something that would be detected and optimized away. Additionally, even with runtime optimizations, something like escape analysis is still required since #method returns a Method object that you'd have to see inside and extract the information from. On the invocation side, the invoked method can only do something special with the block in the case of block inlining, an optimization that only Rubinius has.

    So to get to your questions:

    1. Does Rubinius optimize this code? No. Could it? Yes, but it's hardly easy.
    2. In time it could, yes.
    3. In time it should, yes.

    Note: the questions he refers to in the last paragraph are:

    1. Does Rubinius currently optimize such point-free code?
    2. If it doesn't, could it?
    3. If it could, should it?
    0 讨论(0)
提交回复
热议问题