Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Optimize CurrentAttributes method generation
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 ```
- Loading branch information
Showing
4 changed files
with
94 additions
and
79 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
# 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters