Skip to content

Instantly share code, notes, and snippets.

@casperisfine
Created August 27, 2024 11:25
Show Gist options
  • Save casperisfine/539e310eadd0cb650fc50ca0d9782569 to your computer and use it in GitHub Desktop.
Save casperisfine/539e310eadd0cb650fc50ca0d9782569 to your computer and use it in GitHub Desktop.
# frozen_string_literal: true
require 'bundler/inline'
gemfile(true) do
source 'https://rubygems.org'
gem 'activesupport', github: 'rails/rails'
gem 'benchmark-ips'
end
require 'benchmark/ips'
require "active_support/core_ext/class/attribute"
module ActiveSupport
module ClassAttribute # :nodoc:
class << self
def redefine(owner, name, value)
if owner.singleton_class?
owner.redefine_method(name) { value }
owner.silence_redefinition_of_method(name)
end
owner.redefine_singleton_method(name) { value }
owner.redefine_singleton_method("#{name}=") do |new_value|
if owner.equal?(self)
value = new_value
else
::ActiveSupport::ClassAttribute.redefine(self, name, new_value)
end
end
end
end
end
end
class Class
def faster_class_attribute(*attrs, instance_accessor: true,
instance_reader: instance_accessor, instance_writer: instance_accessor, instance_predicate: true, default: nil)
class_methods, methods = [], []
attrs.each do |name|
unless name.is_a?(Symbol) || name.is_a?(String)
raise TypeError, "#{name.inspect} is not a symbol nor a string"
end
name = name.to_sym
::ActiveSupport::ClassAttribute.redefine(self, name, default)
unless singleton_class?
methods << <<~RUBY if instance_reader
silence_redefinition_of_method def #{name}
defined?(@#{name}) ? @#{name} : self.class.#{name}
end
RUBY
end
methods << <<~RUBY if instance_writer
silence_redefinition_of_method(:#{name}=)
attr_writer :#{name}
RUBY
if instance_predicate
class_methods << "silence_redefinition_of_method def #{name}?; !!self.#{name}; end"
if instance_reader
methods << "silence_redefinition_of_method def #{name}?; !!self.#{name}; end"
end
end
end
location = caller_locations(1, 1).first
class_eval(["class << self", *class_methods, "end", *methods].join(";").tr("\n", ";"), location.path, location.lineno)
end
end
class Foo
class_attribute :old, :old_too
faster_class_attribute :fast, :fast_too
end
class Bar < Foo
end
class Baz < Bar
end
class Other
end
FOO_INSTANCE = Foo.new
puts "==== definition ===="
Benchmark.ips do |x|
x.report("old") { Other.class_attribute :attr1, :attr2, :attr3, :attr4, :attr5, :attr6, :attr7, :attr8, :attr9, :attr10 }
x.report("new") { Other.faster_class_attribute :attr1, :attr2, :attr3, :attr4, :attr5, :attr6, :attr7, :attr8, :attr9, :attr10 }
x.compare!(order: :baseline)
end
puts "==== class reader ===="
Benchmark.ips do |x|
x.report("old") { Foo.old }
x.report("new") { Foo.fast }
x.compare!(order: :baseline)
end
puts "==== class inherited reader ===="
Benchmark.ips do |x|
x.report("old") { Baz.old }
x.report("new") { Baz.fast }
x.compare!(order: :baseline)
end
puts "==== class writer ===="
Benchmark.ips do |x|
x.report("old") { Foo.old = 42 }
x.report("new") { Foo.fast = 42 }
x.compare!(order: :baseline)
end
puts "==== instance reader ===="
Benchmark.ips do |x|
x.report("old") { FOO_INSTANCE.old }
x.report("new") { FOO_INSTANCE.fast }
x.compare!(order: :baseline)
end
puts "==== instance writer ===="
Benchmark.ips do |x|
x.report("old") { FOO_INSTANCE.old = 42}
x.report("new") { FOO_INSTANCE.fast = 42 }
x.compare!(order: :baseline)
end
$ ruby /tmp/class_attribute.rb
Fetching gem metadata from https://rubygems.org/..........
Resolving dependencies...
==== definition ====
ruby 3.3.4 (2024-07-09 revision be1089c8ec) [arm64-darwin23]
Warming up --------------------------------------
old 359.000 i/100ms
new 648.000 i/100ms
Calculating -------------------------------------
old 3.782k (± 4.4%) i/s - 19.027k in 5.040409s
new 6.684k (± 3.7%) i/s - 33.696k in 5.047975s
Comparison:
old: 3782.3 i/s
new: 6684.2 i/s - 1.77x faster
==== class reader ====
ruby 3.3.4 (2024-07-09 revision be1089c8ec) [arm64-darwin23]
Warming up --------------------------------------
old 3.472M i/100ms
new 3.477M i/100ms
Calculating -------------------------------------
old 34.627M (± 1.5%) i/s - 173.583M in 5.014081s
new 33.503M (± 1.3%) i/s - 170.353M in 5.085518s
Comparison:
old: 34627278.8 i/s
new: 33503236.6 i/s - 1.03x slower
==== class inherited reader ====
ruby 3.3.4 (2024-07-09 revision be1089c8ec) [arm64-darwin23]
Warming up --------------------------------------
old 3.543M i/100ms
new 3.539M i/100ms
Calculating -------------------------------------
old 34.971M (± 2.5%) i/s - 177.131M in 5.068421s
new 35.316M (± 0.7%) i/s - 176.926M in 5.010014s
Comparison:
old: 34971144.7 i/s
new: 35316427.3 i/s - same-ish: difference falls within error
==== class writer ====
ruby 3.3.4 (2024-07-09 revision be1089c8ec) [arm64-darwin23]
Warming up --------------------------------------
old 184.659k i/100ms
new 1.769M i/100ms
Calculating -------------------------------------
old 1.834M (± 1.5%) i/s - 9.233M in 5.035024s
new 17.722M (± 2.9%) i/s - 90.220M in 5.095668s
Comparison:
old: 1834149.6 i/s
new: 17722346.6 i/s - 9.66x faster
==== instance reader ====
ruby 3.3.4 (2024-07-09 revision be1089c8ec) [arm64-darwin23]
Warming up --------------------------------------
old 1.807M i/100ms
new 1.804M i/100ms
Calculating -------------------------------------
old 18.065M (± 2.7%) i/s - 90.366M in 5.006426s
new 18.149M (± 1.0%) i/s - 91.989M in 5.068965s
Comparison:
old: 18065296.8 i/s
new: 18149220.0 i/s - same-ish: difference falls within error
==== instance writer ====
ruby 3.3.4 (2024-07-09 revision be1089c8ec) [arm64-darwin23]
Warming up --------------------------------------
old 3.888M i/100ms
new 3.829M i/100ms
Calculating -------------------------------------
old 38.740M (± 2.8%) i/s - 194.413M in 5.022806s
new 38.528M (± 2.4%) i/s - 195.269M in 5.071387s
Comparison:
old: 38740467.7 i/s
new: 38528036.9 i/s - same-ish: difference falls within error
$ ruby --yjit /tmp/class_attribute.rb
Fetching gem metadata from https://rubygems.org/..........
Resolving dependencies...
==== definition ====
ruby 3.3.4 (2024-07-09 revision be1089c8ec) +YJIT [arm64-darwin23]
Warming up --------------------------------------
old 363.000 i/100ms
new 671.000 i/100ms
Calculating -------------------------------------
old 3.745k (± 3.6%) i/s - 18.876k in 5.047932s
new 6.612k (± 3.2%) i/s - 33.550k in 5.079578s
Comparison:
old: 3744.5 i/s
new: 6611.9 i/s - 1.77x faster
==== class reader ====
ruby 3.3.4 (2024-07-09 revision be1089c8ec) +YJIT [arm64-darwin23]
Warming up --------------------------------------
old 4.406M i/100ms
new 4.269M i/100ms
Calculating -------------------------------------
old 43.817M (± 4.5%) i/s - 220.324M in 5.038915s
new 44.779M (± 2.8%) i/s - 226.263M in 5.057146s
Comparison:
old: 43816694.4 i/s
new: 44778662.3 i/s - same-ish: difference falls within error
==== class inherited reader ====
ruby 3.3.4 (2024-07-09 revision be1089c8ec) +YJIT [arm64-darwin23]
Warming up --------------------------------------
old 4.609M i/100ms
new 4.420M i/100ms
Calculating -------------------------------------
old 44.458M (± 4.5%) i/s - 225.817M in 5.090329s
new 44.931M (± 3.2%) i/s - 225.431M in 5.022544s
Comparison:
old: 44457947.2 i/s
new: 44930756.7 i/s - same-ish: difference falls within error
==== class writer ====
ruby 3.3.4 (2024-07-09 revision be1089c8ec) +YJIT [arm64-darwin23]
Warming up --------------------------------------
old 251.197k i/100ms
new 4.098M i/100ms
Calculating -------------------------------------
old 2.561M (± 2.1%) i/s - 12.811M in 5.004196s
new 43.004M (± 3.3%) i/s - 217.210M in 5.056701s
Comparison:
old: 2561173.8 i/s
new: 43004422.5 i/s - 16.79x faster
==== instance reader ====
ruby 3.3.4 (2024-07-09 revision be1089c8ec) +YJIT [arm64-darwin23]
Warming up --------------------------------------
old 4.007M i/100ms
new 3.908M i/100ms
Calculating -------------------------------------
old 38.408M (± 4.3%) i/s - 192.318M in 5.017940s
new 38.316M (± 3.3%) i/s - 195.391M in 5.105697s
Comparison:
old: 38408490.1 i/s
new: 38316383.2 i/s - same-ish: difference falls within error
==== instance writer ====
ruby 3.3.4 (2024-07-09 revision be1089c8ec) +YJIT [arm64-darwin23]
Warming up --------------------------------------
old 3.914M i/100ms
new 4.035M i/100ms
Calculating -------------------------------------
old 40.173M (± 1.9%) i/s - 203.548M in 5.068536s
new 40.273M (± 3.1%) i/s - 201.771M in 5.015094s
Comparison:
old: 40172937.9 i/s
new: 40273498.6 i/s - same-ish: difference falls within error
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment