This blog is part of our Ruby 2.5 series.
1irb> def greet 2irb> yield 3irb> end 4 => :greet 5irb> 6irb> def greet_with_welcome(&block) 7irb> puts 'Welcome' 8irb> greet(&block) 9irb> end 10 => :greet_with_welcome 11irb> greet_with_welcome { p 'BigBinary' } 12Welcome 13"BigBinary" 14 => "BigBinary"
In Ruby 2.4 when we pass a block to a method, which further passes to another method, Ruby creates a new Proc object by the given block before passing this proc to the another method.
This creates unnecessary objects even when the block parameter is not accessed. It also creates a chain of Proc objects when the block parameter is passed through various methods.
Proc creation is one a heavyweight operation because we need to store all local variables (represented by Env objects in MRI internal) in the heap.
Ruby 2.5 introduced a lazy proc allocation. Ruby 2.5 will not create a Proc object when passing a block to another method. Insead, it will pass the block information. If the block is accessed somewhere else, then it creates a Proc object by the given block.
This results in lesser memory allocation and faster execution.
Ruby 2.4
1irb> require 'benchmark' 2 => true 3irb> def greet 4irb> yield 5irb> end 6 => :greet 7irb> 8irb> def greet_with_welcome(&block) 9irb> puts 'Welcome' 10irb> greet(&block) 11irb> end 12 => :greet_with_welcome 13irb> 14irb> Benchmark.measure { 1000.times { greet_with_welcome { 'BigBinary' } } } 15Welcome 16Welcome 17... 18... 19... 20 => #<Benchmark::Tms:0x007fe6ab929de0 @label="", @real=0.022295999999187188, @cstime=0.0, @cutime=0.0, @stime=0.01, @utime=0.0, @total=0.01>
Ruby 2.5
1irb> require 'benchmark' 2 => true 3irb> def greet 4irb> yield 5irb> end 6 => :greet 7irb> 8irb> def greet_with_welcome(&block) 9irb> puts 'Welcome' 10irb> greet(&block) 11irb> end 12 => :greet_with_welcome 13irb> 14 irb> Benchmark.measure { 1000.times { greet_with_welcome { 'BigBinary' } } } 15Welcome 16Welcome 17... 18... 19... 20 => #<Benchmark::Tms:0x00007fa4400871b8 @label="", @real=0.004612999997334555, @cstime=0.0, @cutime=0.0, @stime=0.001524000000000001, @utime=0.0030690000000000023, @total=0.004593000000000003>
As we can see, there is considerable improvement in execution time when a block param is passed in Ruby 2.5.
Here is the relevant commit and discussion.