Back to Blog

Ruby 2.5 added lazy proc allocation for block parameters

on May 22, 2018
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.