From f04c5d640d5703a66193690d1b5948f9f04d4018 Mon Sep 17 00:00:00 2001 From: Lawrence Pit Date: Fri, 15 Jul 2011 09:58:46 +1000 Subject: [PATCH 1/3] Optimization of ActiveModel's match_attribute_method? --- activemodel/lib/active_model/attribute_methods.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index 6ee5e04267..bb5c511a5e 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -410,13 +410,16 @@ module ActiveModel private # Returns a struct representing the matching attribute method. # The struct's attributes are prefix, base and suffix. + @@match_attribute_method_cache = {} def match_attribute_method?(method_name) + cache = @@match_attribute_method_cache[self.class] ||= {} + return cache[method_name] if cache.key?(method_name) self.class.attribute_method_matchers.each do |method| if (match = method.match(method_name)) && attribute_method?(match.attr_name) - return match + return cache[method_name] = match end end - nil + cache[method_name] = nil end # prevent method_missing from calling private methods with #send From c3dd4c653d0a83b3fbb5c05eb93ee824eb66c944 Mon Sep 17 00:00:00 2001 From: Lawrence Pit Date: Sat, 16 Jul 2011 16:40:11 +1000 Subject: [PATCH 2/3] Issue #2075 Optimization of ActiveModel's match_attribute_method? --- .../lib/active_model/attribute_methods.rb | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index bb5c511a5e..15abab87d7 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -61,6 +61,9 @@ module ActiveModel included do class_attribute :attribute_method_matchers, :instance_writer => false self.attribute_method_matchers = [] + + class_attribute :attribute_method_matchers_cache, :instance_writer => false + self.attribute_method_matchers_cache = {} end module ClassMethods @@ -314,6 +317,7 @@ module ActiveModel end end end + attribute_method_matchers_cache.clear end # Removes all the previously dynamically defined methods from the class @@ -321,6 +325,7 @@ module ActiveModel generated_attribute_methods.module_eval do instance_methods.each { |m| undef_method(m) } end + attribute_method_matchers_cache.clear end # Returns true if the attribute methods defined have been generated. @@ -338,6 +343,16 @@ module ActiveModel end private + def attribute_method_matcher(method_name) + if attribute_method_matchers_cache.key?(method_name) + attribute_method_matchers_cache[method_name] + else + match = nil + attribute_method_matchers.detect { |method| match = method.match(method_name) } + attribute_method_matchers_cache[method_name] = match + end + end + class AttributeMethodMatcher attr_reader :prefix, :suffix, :method_missing_target @@ -410,16 +425,9 @@ module ActiveModel private # Returns a struct representing the matching attribute method. # The struct's attributes are prefix, base and suffix. - @@match_attribute_method_cache = {} def match_attribute_method?(method_name) - cache = @@match_attribute_method_cache[self.class] ||= {} - return cache[method_name] if cache.key?(method_name) - self.class.attribute_method_matchers.each do |method| - if (match = method.match(method_name)) && attribute_method?(match.attr_name) - return cache[method_name] = match - end - end - cache[method_name] = nil + match = self.class.send(:attribute_method_matcher, method_name) + match && attribute_method?(match.attr_name) ? match : nil end # prevent method_missing from calling private methods with #send From 52a096275a32c0c5d5125280f144e1e0d893d1f3 Mon Sep 17 00:00:00 2001 From: Lawrence Pit Date: Sun, 17 Jul 2011 14:51:34 +1000 Subject: [PATCH 3/3] Made attribute_method_matchers_cache private + doc --- .../lib/active_model/attribute_methods.rb | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index 15abab87d7..bdc0eb4a0d 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -61,9 +61,6 @@ module ActiveModel included do class_attribute :attribute_method_matchers, :instance_writer => false self.attribute_method_matchers = [] - - class_attribute :attribute_method_matchers_cache, :instance_writer => false - self.attribute_method_matchers_cache = {} end module ClassMethods @@ -343,6 +340,19 @@ module ActiveModel end private + # The methods +method_missing+ and +respond_to?+ of this module are + # invoked often in a typical rails, both of which invoke the method + # +match_attribute_method?+. The latter method iterates through an + # array doing regular expression matches, which results in a lot of + # object creations. Most of the times it returns a +nil+ match. As the + # match result is always the same given a +method_name+, this cache is + # used to alleviate the GC, which ultimately also speeds up the app + # significantly (in our case our test suite finishes 10% faster with + # this cache). + def attribute_method_matchers_cache + @attribute_method_matchers_cache ||= {} + end + def attribute_method_matcher(method_name) if attribute_method_matchers_cache.key?(method_name) attribute_method_matchers_cache[method_name]