BigBinary Blog

We write about Ruby on Rails, React.js, React Native, remote work, open source, engineering and design.

Ruby 2.5 added lazy proc allocation for block parameters

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.

Amit Choudhary in Ruby 2.5
May 22, 2018
Share

Subscribe to our newsletter