From: "tenderlovemaking (Aaron Patterson) via ruby-core" Date: 2025-01-16T21:38:48+00:00 Subject: [ruby-core:120721] [Ruby master Bug#19165] Method (with no param) delegation with *, **, and ... is slow Issue #19165 has been updated by tenderlovemaking (Aaron Patterson). I reran this benchmark with Ruby 3.5. I think most numbers have improved: ``` $ ruby test.rb ruby 3.5.0dev (2025-01-16T16:20:06Z master d05f6a9b8f) +PRISM [arm64-darwin24] Warming up -------------------------------------- simple call 3.459M i/100ms delegate without splat 2.703M i/100ms delegate with splat 1.343M i/100ms delegate with splat with name 1.347M i/100ms delegate with splat and double splat 1.301M i/100ms delegate with triple dots 2.109M i/100ms delegate via forwardable 1.125M i/100ms Calculating ------------------------------------- simple call 34.395M (� 0.4%) i/s (29.07 ns/i) - 172.947M in 5.028381s delegate without splat 27.450M (� 1.5%) i/s (36.43 ns/i) - 137.830M in 5.022175s delegate with splat 13.360M (� 0.5%) i/s (74.85 ns/i) - 67.155M in 5.026767s delegate with splat with name 13.662M (� 0.3%) i/s (73.20 ns/i) - 68.678M in 5.027114s delegate with splat and double splat 13.717M (� 0.5%) i/s (72.90 ns/i) - 68.952M in 5.026767s delegate with triple dots 20.958M (� 0.9%) i/s (47.71 ns/i) - 105.464M in 5.032448s delegate via forwardable 11.273M (� 0.7%) i/s (88.71 ns/i) - 57.368M in 5.089149s Comparison: simple call: 34394553.5 i/s delegate without splat: 27450367.6 i/s - 1.25x slower delegate with triple dots: 20958426.8 i/s - 1.64x slower delegate with splat and double splat: 13717235.5 i/s - 2.51x slower delegate with splat with name: 13661705.2 i/s - 2.52x slower delegate with splat: 13359936.0 i/s - 2.57x slower delegate via forwardable: 11273246.8 i/s - 3.05x slower ``` I think we could probably do something to speed up anonymous `*` and anonymous `**`, but I'm not sure why use those and not `...`. I guess there is a reason, but I also bet most cases in Rails that use anonymous `*` and `**` can be changed to use `...` without any impact to behavior. ---------------------------------------- Bug #19165: Method (with no param) delegation with *, **, and ... is slow https://bugs.ruby-lang.org/issues/19165#change-111554 * Author: matsuda (Akira Matsuda) * Status: Open * ruby -v: ruby 3.2.0dev (2022-12-01T08:05:41Z master 4e68b59431) +YJIT [arm64-darwin21] * Backport: 2.7: UNKNOWN, 3.0: UNKNOWN, 3.1: UNKNOWN ---------------------------------------- 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. -- https://bugs.ruby-lang.org/ ______________________________________________ ruby-core mailing list -- ruby-core@ml.ruby-lang.org To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/