Project

General

Profile

Actions

Bug #19165

open

Method (with no param) delegation with *, **, and ... is slow

Added by matsuda (Akira Matsuda) over 1 year ago. Updated over 1 year ago.

Status:
Open
Assignee:
-
Target version:
-
ruby -v:
ruby 3.2.0dev (2022-12-01T08:05:41Z master 4e68b59431) +YJIT [arm64-darwin21]
[ruby-core:111121]

Description

I found that method delegation via Forwardable is much slower than normal method call when delegating a method that does not take parameters.

Here's a benchmark that explains what I mean.

require 'forwardable'
require 'pp'
require 'benchmark/ips'

class Obj
  extend Forwardable

  attr_accessor :other

  def initialize
    @other = Other.new
  end

  def foo_without_splat
    @other.foo
  end

  def foo_with_splat(*)
    @other.foo(*)
  end

  def foo_with_splat_with_name(*args)
    @other.foo(*args)
  end

  def foo_with_splat_and_double_splat(*, **)
    @other.foo(*, **)
  end

  def foo_with_triple_dots(...)
    @other.foo(...)
  end

  delegate :foo => :@other
end

class Other
  def foo() end
end

o = Obj.new

Benchmark.ips do |x|
  x.report 'simple call' do
    o.other.foo
  end

  x.report 'delegate without splat' do
    o.foo_without_splat
  end

  x.report 'delegate with splat' do
    o.foo_with_splat
  end

  x.report 'delegate with splat with name' do
    o.foo_with_splat_with_name
  end

  x.report 'delegate with splat and double splat' do
    o.foo_with_splat_and_double_splat
  end

  x.report 'delegate with triple dots' do
    o.foo_with_triple_dots
  end

  x.report 'delegate via forwardable' do
    o.foo
  end
end


(result)
         simple call     38.918M (± 0.9%) i/s -    194.884M
delegate without splat
                         31.933M (± 1.6%) i/s -    159.611M
 delegate with splat     10.269M (± 1.6%) i/s -     51.631M
delegate with splat with name
                          9.888M (± 1.0%) i/s -     49.588M
delegate with splat and double splat
                          4.117M (± 0.9%) i/s -     20.696M
delegate with triple dots
                          4.169M (± 0.9%) i/s -     20.857M
delegate via forwardable
                          9.204M (± 2.1%) i/s -     46.295M

It shows that Method delegation with a splat is 3-4 times slower (regardless of whether the parameter is named or not), and delegation with a triple-dot literal is 9-10 times slower than a method delegation without an argument.
This may be because calling a method taking a splat always assigns an Array object even when no actual argument was given, and calling a method taking triple-dots assigns five Array objects and two Hash objects (this is equivalent to *, **).

Are there any chance reducing these object assignments and making them faster? My concern is that the Rails framework heavily uses this kind of method delegations, and presumably it causes unignorable performance overhead.


Related issues 1 (0 open1 closed)

Related to Ruby master - Feature #19134: ** is not allowed in def foo(...)Closedmatz (Yukihiro Matsumoto)Actions
Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0