**** EDIT – New stuff added 9/28 at the bottom to fix this and make it more accurate, thanks to reddit user estum ****
Somehow or another I ended up reading about procs and lambdas in Ruby today, and since I personally haven’t really used them, I wondered if they have any performance benefits over using a block like I normally do. So I tested it out with benchmark. This article is pretty helpful, with examples. There is also the ruby docs if you prefer that (this link goes to the doc on procs).
I spun up a ruby file and used this to test it out.
require 'benchmark'
test_proc = Proc.new { |x| x * x }
test_lambda = ->(x) { x * x }
def call_block(x)
yield(x)
end
n = 1_000_000_000
Benchmark.bm do |x|
x.report("block:") { n.times { call_block(5) { |x| x * x } } }
x.report("proc:") { n.times { test_proc.call(5) } }
x.report("lambda:") { n.times { test_lambda.call(5) } }
end
A quick run through on what is going on here ^^
Make sure you require benchmark at the top
require 'benchmark'
And then create a proc and lambda
test_proc = Proc.new { |x| x * x }
test_lambda = ->(x) { x * x }
As well as a block
def call_block(x)
yield(x)
end
and then set the number of iterations to see how fast it goes (setting it at a billion took some time, so next time maybe I’ll do a smaller number)
n = 1_000_000_000
That is going to call each of them a billion times to assess the performance
And finally, setting up something to assess the performance
Benchmark.bm do |x|
x.report("block:") { n.times { call_block(5) { |x| x * x } } }
x.report("proc:") { n.times { test_proc.call(5) } }
x.report("lambda:") { n.times { test_lambda.call(5) } }
end
It is going to set x as 5 to do 5 * 5 3 billion times total
And then I run it
ruby proc_lambda.rb
And here are the results
user system total real
block: 41.166534 0.101327 41.267861 ( 41.412893)
proc: 36.837533 0.078706 36.916239 ( 36.960835)
lambda: 36.432841 0.077678 36.510519 ( 36.594011)
So everything I read said that block should have been quite a bit faster. But the way I have it is increasing the overhead when you use the method call_block that has a yield inside of it, instead of just going straight to the block. The proc and lambda only needed to get created once, and the block was passing a method every single time. So here is a solution to not calling the method every time and just passing it directly.
require 'benchmark'
test_proc = Proc.new { |x| x * x }
test_lambda = ->(x) { x * x }
n = 1_000_000_000
Benchmark.bm do |x|
# No method, just a block
x.report("block:") { n.times { |i| i * i } }
# same as earlier
x.report("proc:") { n.times { test_proc.call(5) } }
# same as earlier
x.report("lambda:") { n.times { test_lambda.call(5) } }
end
So I probably should have done it like that the first time, but now I know. Here is the benchmark for that one.
user system total real
block: 19.368932 0.041246 19.410178 ( 19.410762)
proc: 36.174256 0.100088 36.274344 ( 36.415370)
lambda: 35.885171 0.070823 35.955994 ( 35.957910)
And it was pointed out that what I did wasn’t exactly accurate. Part of the reason I post this stuff is because I still have a ton to learn, so that works out great.
I needed to update the way that proc and lambda were being passed into the loop and avoid unnecessary overhead. I think .call was getting used way too often, and needed to be replaced with & to pass them directly to n.times. Like this
Benchmark.bm do |x|
x.report("block:") { n.times { |i| i * i } }
x.report("proc:") { n.times(&test_proc) }
x.report("lambda:") { n.times(&test_lambda) }
end
So not as drastic as the split above, but a little faster still.
user system total real
block: 33.301507 0.070785 33.372292 ( 33.379451)
proc: 35.709916 0.101752 35.811668 ( 35.992860)
lambda: 35.555532 0.107621 35.663153 ( 35.837953)
I think this is more accurate now, but if I’m wrong, please let me know
Leave a Reply