Skip to content

Commit 1bf50ba

Browse files
committed
Decrease string allocations in apply_inflections
In `apply_inflections` a string is down cased and some whitespace stripped in the front (which allocate strings). This would normally be fine, however `uncountables` is a fairly small array (10 elements out of the box) and this method gets called a TON. Instead we can keep an array of valid regexes for each uncountable so we don't have to allocate new strings. This change buys us 325,106 bytes of memory and 3,251 fewer objects per request.
1 parent f80aa59 commit 1bf50ba

File tree

2 files changed

+36
-5
lines changed

2 files changed

+36
-5
lines changed

activesupport/lib/active_support/inflector/inflections.rb

+34-3
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,45 @@ module Inflector
2727
class Inflections
2828
@__instance__ = ThreadSafe::Cache.new
2929

30+
class Uncountables < Array
31+
def initialize
32+
@regex_array = []
33+
super
34+
end
35+
36+
def delete(entry)
37+
super entry
38+
@regex_array.delete(to_regex(entry))
39+
end
40+
41+
def <<(*word)
42+
add(word)
43+
end
44+
45+
def add(words)
46+
self.concat(words.flatten.map(&:downcase))
47+
@regex_array += self.map {|word| to_regex(word) }
48+
self
49+
end
50+
51+
def uncountable?(str)
52+
@regex_array.detect {|regex| regex.match(str) }
53+
end
54+
55+
private
56+
def to_regex(string)
57+
/\b#{::Regexp.escape(string)}\Z/i
58+
end
59+
end
60+
3061
def self.instance(locale = :en)
3162
@__instance__[locale] ||= new
3263
end
3364

3465
attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex
3566

3667
def initialize
37-
@plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], [], [], {}, /(?=a)b/
68+
@plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], Uncountables.new, [], {}, /(?=a)b/
3869
end
3970

4071
# Private, for the test suite.
@@ -160,7 +191,7 @@ def irregular(singular, plural)
160191
# uncountable 'money', 'information'
161192
# uncountable %w( money information rice )
162193
def uncountable(*words)
163-
@uncountables += words.flatten.map(&:downcase)
194+
@uncountables.add(words)
164195
end
165196

166197
# Specifies a humanized form of a string by a regular expression rule or
@@ -185,7 +216,7 @@ def human(rule, replacement)
185216
def clear(scope = :all)
186217
case scope
187218
when :all
188-
@plurals, @singulars, @uncountables, @humans = [], [], [], []
219+
@plurals, @singulars, @uncountables, @humans = [], [], Uncountables.new, []
189220
else
190221
instance_variable_set "@#{scope}", []
191222
end

activesupport/lib/active_support/inflector/methods.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ def ordinalize(number)
354354
# const_regexp("Foo::Bar::Baz") # => "Foo(::Bar(::Baz)?)?"
355355
# const_regexp("::") # => "::"
356356
def const_regexp(camel_cased_word) #:nodoc:
357-
parts = camel_cased_word.split("::")
357+
parts = camel_cased_word.split("::".freeze)
358358

359359
return Regexp.escape(camel_cased_word) if parts.blank?
360360

@@ -372,7 +372,7 @@ def const_regexp(camel_cased_word) #:nodoc:
372372
def apply_inflections(word, rules)
373373
result = word.to_s.dup
374374

375-
if word.empty? || inflections.uncountables.include?(result.downcase[/\b\w+\Z/])
375+
if word.empty? || inflections.uncountables.uncountable?(result)
376376
result
377377
else
378378
rules.each { |(rule, replacement)| break if result.sub!(rule, replacement) }

0 commit comments

Comments
 (0)