mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
bf33510d86
The bulk of the optimization is to generate code rather than use `define_method` with a closure. ``` Warming up -------------------------------------- original 207.468k i/100ms code-generator 340.849k i/100ms Calculating ------------------------------------- original 2.127M (± 1.1%) i/s - 10.788M in 5.073860s code-generator 3.426M (± 0.9%) i/s - 17.383M in 5.073965s Comparison: code-generator: 3426241.0 i/s original: 2126539.2 i/s - 1.61x (± 0.00) slower ``` ```ruby require 'benchmark/ips' require 'active_support/all' class Original < ActiveSupport::CurrentAttributes attribute :foo end class CodeGen < ActiveSupport::CurrentAttributes class << self def attribute(*names) ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |owner| names.each do |name| owner.define_cached_method(name, namespace: :current_attributes) do |batch| batch << "def #{name}" << "attributes[:#{name}]" << "end" end owner.define_cached_method("#{name}=", namespace: :current_attributes) do |batch| batch << "def #{name}=(value)" << "attributes[:#{name}] = value" << "end" end end end ActiveSupport::CodeGenerator.batch(singleton_class, __FILE__, __LINE__) do |owner| names.each do |name| owner.define_cached_method(name, namespace: :current_attributes_delegation) do |batch| batch << "def #{name}" << "instance.#{name}" << "end" end owner.define_cached_method("#{name}=", namespace: :current_attributes_delegation) do |batch| batch << "def #{name}=(value)" << "instance.#{name} = value" << "end" end end end end end attribute :foo end Benchmark.ips do |x| x.report('original') { Original.foo } x.report('code-generator') { CodeGen.foo } x.compare! end ```
65 lines
1.6 KiB
Ruby
65 lines
1.6 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module ActiveSupport
|
|
class CodeGenerator # :nodoc:
|
|
class MethodSet
|
|
METHOD_CACHES = Hash.new { |h, k| h[k] = Module.new }
|
|
|
|
def initialize(namespace)
|
|
@cache = METHOD_CACHES[namespace]
|
|
@sources = []
|
|
@methods = {}
|
|
end
|
|
|
|
def define_cached_method(name, as: name)
|
|
name = name.to_sym
|
|
as = as.to_sym
|
|
@methods.fetch(name) do
|
|
unless @cache.method_defined?(as)
|
|
yield @sources
|
|
end
|
|
@methods[name] = as
|
|
end
|
|
end
|
|
|
|
def apply(owner, path, line)
|
|
unless @sources.empty?
|
|
@cache.module_eval("# frozen_string_literal: true\n" + @sources.join(";"), path, line)
|
|
end
|
|
@methods.each do |name, as|
|
|
owner.define_method(name, @cache.instance_method(as))
|
|
end
|
|
end
|
|
end
|
|
|
|
class << self
|
|
def batch(owner, path, line)
|
|
if owner.is_a?(CodeGenerator)
|
|
yield owner
|
|
else
|
|
instance = new(owner, path, line)
|
|
result = yield instance
|
|
instance.execute
|
|
result
|
|
end
|
|
end
|
|
end
|
|
|
|
def initialize(owner, path, line)
|
|
@owner = owner
|
|
@path = path
|
|
@line = line
|
|
@namespaces = Hash.new { |h, k| h[k] = MethodSet.new(k) }
|
|
end
|
|
|
|
def define_cached_method(name, namespace:, as: name, &block)
|
|
@namespaces[namespace].define_cached_method(name, as: as, &block)
|
|
end
|
|
|
|
def execute
|
|
@namespaces.each_value do |method_set|
|
|
method_set.apply(@owner, @path, @line - 1)
|
|
end
|
|
end
|
|
end
|
|
end
|