From: "byroot (Jean Boussier) via ruby-core" Date: 2025-04-07T14:43:17+00:00 Subject: [ruby-core:121554] [Ruby Bug#21201] Performance regression when defining methods inside `refine` blocks Issue #21201 has been updated by byroot (Jean Boussier). Your patch look really good. I wonder if it would be possible to do like the `vm->constant_cache` table, have the key be the method name, so that you wouldn't need to clear absolutely everything. But your patch as-is is already a nice improvement I think, the only downside being the one extra lock point for ractors. ---------------------------------------- Bug #21201: Performance regression when defining methods inside `refine` blocks https://bugs.ruby-lang.org/issues/21201#change-112581 * Author: alpaca-tc (Hiroyuki Ishii) * Status: Open * ruby -v: 3.3.7, 3.4.2, 3.5.0 * Backport: 3.1: UNKNOWN, 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN ---------------------------------------- After the change introduced in [PR #10037](https://github.com/ruby/ruby/pull/10037), a significant performance regression has been observed when defining methods within refine blocks. The PR correctly fixes a bug where `callcache` invalidation was missing when methods are defined inside a refinement. To fix this, it now invokes [`rb_clear_all_refinement_method_cache()`](https://github.com/ruby/ruby/blob/752a1d785475d1950b33c7d77b289a6a3a753f02/vm_method.c#L439-L443) at the time of method definition within `refine`. However, this function performs a full object space traversal via `rb_objspace_each_objects`, making it extremely expensive. In our team's Rails application, refinements are used heavily. During application code reload (triggered in development mode), `rb_clear_all_refinement_method_cache()` is called 1425 times, 1142 of which originate via `rb_clear_method_cache()`. As a result, the code reload time has increased from **5 seconds** to **15 seconds** after the patch. Since reloading occurs every time the code changes, this degrades development experience severely. ### Reproduction script: ```ruby require "bundler/inline" gemfile(true) do source "https://rubygems.org" gem "benchmark-ips" end mod = Module.new klass = Class.new Benchmark.ips do |x| x.report(RUBY_VERSION) do mod.send(:refine, klass) do def call_1 = nil def call_2 = nil def call_3 = nil end end x.save! "/tmp/performance_regression_refine.bench" x.compare! end ``` ### Benchmark results: ``` Comparison: 3.2.7: 1500316.7 i/s 3.3.7: 158.0 i/s - 9496.46x slower 3.5.0: 124.6 i/s - 12041.43x slower 3.4.2: 119.5 i/s - 12553.50x slower ``` Related Slack discussion: https://ruby-jp.slack.com/archives/CLWSHA76V/p1741932018811539 I totally understand the necessity and value of the past PR that fixed the callcache bugs. That said, I was wondering if there might be any ideas to make it faster, or to handle the invalidation more efficiently. -- 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/