Skip to content

Instantly share code, notes, and snippets.

@ardecvz
Last active July 30, 2026 19:52
Show Gist options
  • Select an option

  • Save ardecvz/fdd33501625199018fb698cfea8d8537 to your computer and use it in GitHub Desktop.

Select an option

Save ardecvz/fdd33501625199018fb698cfea8d8537 to your computer and use it in GitHub Desktop.
A benchmark of Ruby refinements activation cost: `using` slows down with the number of classes loaded in the process on Ruby < 4.0 (bug #21201, fixed by the refinement-cache table)
# frozen_string_literal: true
# Activating a refinement (`using`) on Ruby < 4.0 triggers a full object-space
# scan to invalidate method caches, so its cost grows with the number of classes
# already loaded in the process.
# Ref: https://bugs.ruby-lang.org/issues/21201
#
# This benchmark generates N classes, then loads a refinement-heavy gem
# (`anyway_config`). Compare a small and a large N, with refinements on and off:
#
# N=4000 ruby refinements_activation_bench.rb
# N=30000 ruby refinements_activation_bench.rb
#
# NOUSING=1 N=4000 ruby refinements_activation_bench.rb
# NOUSING=1 N=30000 ruby refinements_activation_bench.rb
#
# 4,000 and 30,000 mirror a real Rails monolith's class count
# right after `rails/all` and after the full gem list, respectively.
require "bundler/inline"
gemfile do
source "https://rubygems.org"
gem "benchmark"
gem "anyway_config", "~> 2.8", require: false
end
require "benchmark"
N = ENV.fetch("N", "5").to_i
N.times do |i|
class_name = "Class#{i + 1}"
eval <<-RUBY
class #{class_name}
def initialize
@name = '#{class_name}'
end
def greet
puts "Hello from \#{@name}!"
end
end
RUBY
end
if ENV["NOUSING"] == "1"
class Module
def using(*) = nil
def refine(*, &) = nil
end
end
res = Benchmark.measure do
require "anyway_config"
end
puts res.real

Ruby 4.0.6 vs. 3.3.8

  • 4.0.6 is 40-67% faster overall across all test sizes.
  • The gap is most significant at N=100,000 (67.3% faster) and smallest at N=5,000 (39.7% faster).

using overhead (refinements on vs. NOUSING=1)

  • At N=5,000: 4.0.6 barely notices (+4.3%), while 3.3.8 already pays +57.8%.
  • At N=30,000: 4.0.6 stays efficient (+11.0%), while 3.3.8 more than doubles the load time (+115.6%).
  • At N=100,000: both versions pay noticeably, but 3.3.8 is dramatically worse (+16.9% vs. +228.0%).

Environment

Measured on x86_64-linux, best of five runs per configuration:

  • ruby 3.3.8 (2025-04-09 revision b200bad6cd)
  • ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +PRISM
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment