Ruby: Proc#call vs yield

后端 未结 6 1481
野性不改
野性不改 2020-12-02 05:54

What are the behavioural differences between the following two implementations in Ruby of the thrice method?

module WithYield
  def self.thrice
         


        
6条回答
  •  天涯浪人
    2020-12-02 06:18

    The other answers are pretty thorough and Closures in Ruby extensively covers the functional differences. I was curious about which method would perform best for methods that optionally accept a block, so I wrote some benchmarks (going off this Paul Mucur post). I compared three methods:

    • &block in method signature
    • Using &Proc.new
    • Wrapping yield in another block

    Here is the code:

    require "benchmark"
    
    def always_yield
      yield
    end
    
    def sometimes_block(flag, &block)
      if flag && block
        always_yield &block
      end
    end
    
    def sometimes_proc_new(flag)
      if flag && block_given?
        always_yield &Proc.new
      end
    end
    
    def sometimes_yield(flag)
      if flag && block_given?
        always_yield { yield }
      end
    end
    
    a = b = c = 0
    n = 1_000_000
    Benchmark.bmbm do |x|
      x.report("no &block") do
        n.times do
          sometimes_block(false) { "won't get used" }
        end
      end
      x.report("no Proc.new") do
        n.times do
          sometimes_proc_new(false) { "won't get used" }
        end
      end
      x.report("no yield") do
        n.times do
          sometimes_yield(false) { "won't get used" }
        end
      end
    
      x.report("&block") do
        n.times do
          sometimes_block(true) { a += 1 }
        end
      end
      x.report("Proc.new") do
        n.times do
          sometimes_proc_new(true) { b += 1 }
        end
      end
      x.report("yield") do
        n.times do
          sometimes_yield(true) { c += 1 }
        end
      end
    end
    

    Performance was similar between Ruby 2.0.0p247 and 1.9.3p392. Here are the results for 1.9.3:

                      user     system      total        real
    no &block     0.580000   0.030000   0.610000 (  0.609523)
    no Proc.new   0.080000   0.000000   0.080000 (  0.076817)
    no yield      0.070000   0.000000   0.070000 (  0.077191)
    &block        0.660000   0.030000   0.690000 (  0.689446)
    Proc.new      0.820000   0.030000   0.850000 (  0.849887)
    yield         0.250000   0.000000   0.250000 (  0.249116)
    

    Adding an explicit &block param when it's not always used really does slow down the method. If the block is optional, do not add it to the method signature. And, for passing blocks around, wrapping yield in another block is fastest.

    That said, these are the results for a million iterations, so don't worry about it too much. If one method makes your code clearer at the expense of a millionth of a second, use it anyway.

提交回复
热议问题