15

This answer on another question says that

array.map(&:to_s)

is faster than

array.map { |n| n.to_s }

In the first example, & turns :to_s into a Proc. The second example uses a block.

Why might a Proc be faster than a block in that benchmark? Is there some optimization that this technique allows the interpreter to do?

Community
  • 1
  • 1
Nathan Long
  • 122,748
  • 97
  • 336
  • 451

3 Answers3

7

It is not really about "proc vs block".

Here is a simple experiment (feel free to copy and run):

require 'benchmark'

many = 500
array = (1..10000).to_a

proc = proc { |a| a.to_s }

Benchmark.bm do |x|
  x.report('Symbol#to_proc') { many.times { array.map(&:to_s) } }
  x.report('proc') { many.times { array.map(&proc) }  }
  x.report('block') { many.times { array.map { |a| a.to_s } }  }
end

Ruby 1.9.3p194 results:

                user     system      total        real
Symbol#to_proc  1.170000   0.000000   1.170000 (  1.169055)
proc            1.450000   0.000000   1.450000 (  1.454216)
block           1.450000   0.000000   1.450000 (  1.448094)

As you see, block and proc both take virtually the same amount of CPU time. The magic is inside Symbol#to_proc itself.

Daniel Vartanov
  • 3,243
  • 1
  • 19
  • 26
  • That is a very strange result. I would have guessed that re-using the same Proc object would be faster than repeatedly creating one from a symbol. Maybe the interpreter somehow skips the work of creating a Proc at all and simply does what the proc would have done? (Update: ah, that's what @FrederickCheung says.) – Nathan Long Jul 20 '12 at 21:32
4

As others have been said this is specifically about Symbol#to_proc rather than procs in general and it is almost certainly ruby implementation dependant. Before Symbol#to_proc was in ruby itself, the pure ruby implementations of it were definitely slower the the equivalent block.

For a real answer you'd want to profile ruby while you're executing such a benchmark.

My reading of the ruby source code is that when you call Symbol#to_proc the proc you get is a bit special: The body of the proc is just a C api call (rb_funcall_passing_block), whereas in the other cases it's actual ruby code which takes a little longer to execute.

Frederick Cheung
  • 83,189
  • 8
  • 152
  • 174
0

Just a guess but perhaps it's because the Proc doesn't need to hold onto the context of the call the same way as the block. The block needs to know variables declared outside of it, the Proc does not.

Simon Chiang
  • 835
  • 1
  • 8
  • 14
  • 1
    I have add here, that Proc in Ruby is a closure too (i.e. saves the context). There is a very simple test to ensure: https://gist.github.com/3109708 – Daniel Vartanov Jul 14 '12 at 06:27