Skip to content

Commit

Permalink
Lower GC pressure in case of call with local variables
Browse files Browse the repository at this point in the history
By using the `missing_interpolation_argument_handler` and not always
merging the globals with local interpolation values, it does not create
a new hash for each call anymore. Lowers GC pressure noticable.
  • Loading branch information
doits committed Sep 9, 2016
1 parent 6b83adf commit d819900
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 5 deletions.
30 changes: 25 additions & 5 deletions lib/i18n/globals.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
require 'i18n/globals/version'

module I18n
FAKE_INTERPOLATION_HASH = { fake_: :_interpolation }.freeze

class Config
class CachedGlobals < Hash
def []=(key, val)
Expand Down Expand Up @@ -79,15 +81,33 @@ def globals
def globals=(new_globals)
globals.clear.merge!(new_globals) # maybe there is something better than `clear` and `merge!`
end

# Overrides original I18n::Config#missing_interpolation_argument_handler
def missing_interpolation_argument_handler
@@missing_interpolation_argument_handler ||= lambda do |missing_key, provided_hash, string|
raise MissingInterpolationArgument.new(missing_key, provided_hash, string)
end

@@missing_interpolation_argument_handler_with_globals ||= lambda do |missing_key, provided_hash, string|
# Since the key was not found in a interpolation variable, check
# whether it is a global. If it is, return it, so interpolation is
# successfull.
if globals.for_locale(locale).key?(missing_key)
globals.for_locale(locale)[missing_key]
else
@@missing_interpolation_argument_handler.call(missing_key, provided_hash, string)
end
end
end
end

class << self
def translate(*args)
if args.last.is_a?(Hash)
args[-1] = config.globals.for_locale(locale).merge(args.last)
else
args << config.globals.for_locale(locale)
end
# If last argument is a hash, interpolation will be run. If not, it will
# not even attempt to interpolate something and our custom
# `missing_interpolation_argument_handler` will not be run at all. Thats
# why we pass in a fake hash so that it always runs interpolation.
args << FAKE_INTERPOLATION_HASH unless args.last.is_a?(Hash)
super(*args)
end

Expand Down
45 changes: 45 additions & 0 deletions test/test_i18n_globals.rb
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,49 @@ def test_that_cache_is_cleared_after_merging_a_new_global

assert_equal 'Hi there, Dobby!', I18n.translate('greeting')
end

def test_it_still_fails_on_missing_interpolation
assert_raises(I18n::MissingInterpolationArgument) { I18n.translate('greeting') }
end

def test_it_allows_to_set_a_custom_missing_interpolation_argument_handler
I18n.config.missing_interpolation_argument_handler = proc { raise 'works!' }

assert_raises('works!') { I18n.translate('greeting') }

I18n.config.missing_interpolation_argument_handler = nil
end

def test_it_translates_globals_with_custom_missing_interpolation_argument_handler
I18n.config.missing_interpolation_argument_handler = proc { raise 'works!' }

I18n.config.globals = {
name: 'Greg'
}

assert_equal I18n.translate('greeting'), 'Hi there, Greg!'

I18n.config.missing_interpolation_argument_handler = nil
end

def test_it_does_not_polute_the_object_space_with_hashes
I18n.config.globals = {
name: 'Greg'
}

values = { name: 'Dobby' }
times = 10_000

GC.disable
count_before = ObjectSpace.each_object(Hash).count
times.times { I18n.translate('greeting', values) }
count_after = ObjectSpace.each_object(Hash).count
GC.enable

# It looks like I18n.translate by default allocates 2 new hashes
# per call. So substract it from the count.
expected_count = count_after - times * 2

assert_operator count_before, :==, expected_count
end
end

0 comments on commit d819900

Please sign in to comment.