Issue #14330 has been reported by ko1 (Koichi Sasada).

----------------------------------------
Feature #14330: Speedup `block.call` where `block` is passed block parameter.
https://bugs.ruby-lang.org/issues/14330

* Author: ko1 (Koichi Sasada)
* Status: Open
* Priority: Normal
* Assignee: ko1 (Koichi Sasada)
* Target version: 2.6
----------------------------------------
# abstract

Speedup `block.call` where `block` is passed block parameter with a special object named "proxyblock" which responds to `call` and invoke passed block.

# background

Ruby 2.5 improved performance of passing a passed block parameter by lazy Proc creation ([Feature #14045]).
However, `block.call` (`block` is a passed block parameter) is not optimized and need to create a `Proc` object immediately.

# proposal

We need to make Proc creation lazily for performance. There are several way to achieve it, but I propose to use special object named "blockproxy" object.

This is a pseudo code to use it:

```ruby
# block is given block parameter

block.call(1, 2, 3)

#=> translate at compile time

if block is not modified and
   block is ISeq/IFunc block
  tmp = blockproxy
else
  tmp = block # create Proc and so on
end
tmp.call(1, 2, 3)
```

`blockproxy.call` invoke given block if `Proc#call` is not redefined, otherwise make a Proc and call `Proc#call` as usual.

Advantage of this method is we can also use this technique with the safe navigation operator (`block&.call`).
If block is not given, then `tmp` will be `nil`, and no method dispatched with `&.`.

Note that this technique depends on the assumption "we can't access to the evaluated receiver just before method dispatch". We don't/can't access `blockproxy` object at method dispatch, and no compatibility issue.

# evaluation

Using https://github.com/k0kubun/benchmark_driver

```
prelude: |
  def block_yield
    yield
  end
  def bp_yield &b
    yield
  end
  def bp_call &b
    b.call
  end
  def bp_safe_call &b
    b&.call
  end

benchmark:
  - block_yield{}
  - bp_yield{}
  - bp_call{}
  - bp_safe_call{}
  - bp_safe_call
```

Result:

```
Warming up --------------------------------------
       block_yield{}     1.298M i/100ms
          bp_yield{}     1.177M i/100ms
           bp_call{}   447.723k i/100ms
      bp_safe_call{}   413.261k i/100ms
        bp_safe_call     2.955M i/100ms
Calculating -------------------------------------
                          trunk    modified
       block_yield{}    20.672M     20.808M i/s -     51.933M in 2.512265s 2.495806s
          bp_yield{}    16.294M     16.220M i/s -     47.099M in 2.890459s 2.903797s
           bp_call{}     5.626M     14.752M i/s -     17.909M in 3.182966s 1.213976s # x2.62
      bp_safe_call{}     5.555M     14.557M i/s -     16.530M in 2.975892s 1.135586s # x2.62
        bp_safe_call    31.339M     23.561M i/s -    118.184M in 3.771157s 5.016200s
```

The patch is here:
https://gist.github.com/ko1/d8a1a9d92075b27a8e95ca528cc57fd2




-- 
https://bugs.ruby-lang.org/

Unsubscribe: <mailto:ruby-core-request / ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>