代码之家  ›  专栏  ›  技术社区  ›  Sam Stokes

Ruby:Proc#通话vs收益

  •  72
  • Sam Stokes  · 技术社区  · 17 年前

    以下两种在Ruby中实现的行为差异是什么 thrice 方法

    module WithYield
      def self.thrice
        3.times { yield }      # yield to the implicit block argument
      end
    end
    
    module WithProcCall
      def self.thrice(&block)  # & converts implicit block to an explicit, named Proc
        3.times { block.call } # invoke Proc#call
      end
    end
    
    WithYield::thrice { puts "Hello world" }
    WithProcCall::thrice { puts "Hello world" }
    

    通过“行为差异”,我包括错误处理、性能、工具支持等。

    7 回复  |  直到 11 年前
        1
  •  54
  •   jpastuszek    17 年前

    我认为第一个事实上是另一个的语法糖。换句话说,没有行为上的差异。

    第二种形式允许将块“保存”到变量中。然后可以在其他时间点调用该块——回调。


    好啊这次我去做了一个快速的基准测试:

    require 'benchmark'
    
    class A
      def test
        10.times do
          yield
        end
      end
    end
    
    class B
      def test(&block)
        10.times do
          block.call
        end
      end
    end
    
    Benchmark.bm do |b|
      b.report do
        a = A.new
        10000.times do
          a.test{ 1 + 1 }
        end
      end
    
      b.report do
        a = B.new
        10000.times do
          a.test{ 1 + 1 }
        end
      end
    
      b.report do
        a = A.new
        100000.times do
          a.test{ 1 + 1 }
        end
      end
    
      b.report do
        a = B.new
        100000.times do
          a.test{ 1 + 1 }
        end
      end
    
    end
    

    结果很有趣:

          user     system      total        real
      0.090000   0.040000   0.130000 (  0.141529)
      0.180000   0.060000   0.240000 (  0.234289)
      0.950000   0.370000   1.320000 (  1.359902)
      1.810000   0.570000   2.380000 (  2.430991)
    

    这表明使用 块呼叫 几乎是使用速度的2倍 产量 .

        2
  •  8
  •   cbrauchli    13 年前

    其他的答案都是非常详尽的 Closures in Ruby 广泛涵盖了功能差异。我很好奇哪种方法对那些 选择性地 接受一个块,所以我写了一些基准测试(即将开始) this Paul Mucur post ).我比较了三种方法:

    • &块内方法签名
    • 使用 &Proc.new
    • 包装 yield 在另一个街区

    以下是代码:

    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
    

    Ruby 2.0.0p247和1.9.3p392的性能相似。以下是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)
    

    添加显式 &block param在不经常使用时确实会减慢方法的速度。如果该块是可选的,则不要将其添加到方法签名中。还有,为了传递方块,包装 产量 在另一个街区是最快的。

    也就是说,这些是一百万次迭代的结果,所以不要太担心。如果有一种方法以百万分之一秒的代价让代码更清晰,那还是使用它吧。

        3
  •  7
  •   Sam Stokes    17 年前

    如果您忘记传递一个块,它们会给出不同的错误消息:

    > WithYield::thrice
    LocalJumpError: no block given
            from (irb):3:in `thrice'
            from (irb):3:in `times'
            from (irb):3:in `thrice'
    
    > WithProcCall::thrice
    NoMethodError: undefined method `call' for nil:NilClass
            from (irb):9:in `thrice'
            from (irb):9:in `times'
            from (irb):9:in `thrice'
    

    但如果你试图传递一个“正常”(非阻塞)参数,它们的行为是相同的:

    > WithYield::thrice(42)
    ArgumentError: wrong number of arguments (1 for 0)
            from (irb):19:in `thrice'
    
    > WithProcCall::thrice(42)
    ArgumentError: wrong number of arguments (1 for 0)
            from (irb):20:in `thrice'
    
        4
  •  2
  •   ioquatix    9 年前

    我发现结果是不同的,这取决于您是否强制Ruby构建块(例如,一个预先存在的proc)。

    require 'benchmark/ips'
    
    puts "Ruby #{RUBY_VERSION} at #{Time.now}"
    puts
    
    firstname = 'soundarapandian'
    middlename = 'rathinasamy'
    lastname = 'arumugam'
    
    def do_call(&block)
        block.call
    end
    
    def do_yield(&block)
        yield
    end
    
    def do_yield_without_block
        yield
    end
    
    existing_block = proc{}
    
    Benchmark.ips do |x|
        x.report("block.call") do |i|
            buffer = String.new
    
            while (i -= 1) > 0
                do_call(&existing_block)
            end
        end
    
        x.report("yield with block") do |i|
            buffer = String.new
    
            while (i -= 1) > 0
                do_yield(&existing_block)
            end
        end
    
        x.report("yield") do |i|
            buffer = String.new
    
            while (i -= 1) > 0
                do_yield_without_block(&existing_block)
            end
        end
    
        x.compare!
    end
    

    给出了结果:

    Ruby 2.3.1 at 2016-11-15 23:55:38 +1300
    
    Warming up --------------------------------------
              block.call   266.502k i/100ms
        yield with block   269.487k i/100ms
                   yield   262.597k i/100ms
    Calculating -------------------------------------
              block.call      8.271M (± 5.4%) i/s -     41.308M in   5.009898s
        yield with block     11.754M (± 4.8%) i/s -     58.748M in   5.011017s
                   yield     16.206M (± 5.6%) i/s -     80.880M in   5.008679s
    
    Comparison:
                   yield: 16206091.2 i/s
        yield with block: 11753521.0 i/s - 1.38x  slower
              block.call:  8271283.9 i/s - 1.96x  slower
    

    如果你改变 do_call(&existing_block) 到 do_call{} 你会发现在这两种情况下都要慢5倍。我认为这样做的原因应该是显而易见的(因为Ruby必须为每次调用构造一个Proc)。

        5
  •  0
  •   Travis Reeder    15 年前

    顺便说一句,只需使用以下工具将此更新到当前日期:

    ruby 1.9.2p180 (2011-02-18 revision 30909) [x86_64-linux]
    

    在英特尔i7(1.5年历史)上。

    user     system      total        real
    0.010000   0.000000   0.010000 (  0.015555)
    0.030000   0.000000   0.030000 (  0.024416)
    0.120000   0.000000   0.120000 (  0.121450)
    0.240000   0.000000   0.240000 (  0.239760)
    

    还是慢了2倍。有趣的

    推荐文章