Skip to content

Commit

Permalink
Reduce memory usage from Formatter cache
Browse files Browse the repository at this point in the history
Previously this used Hashes and Arrays exclusively as an ad-hoc data
structure. This ended up with a lot of empty arrays as well as hashes of
size 1 with only the key ___routes.

This introduces a CacheNode class which we can use to store the routes
list separately (and avoids allocating the array and hash when
unneeded).

This also converts the one level hash with array keys ex.

    { [:controller, "home"] => ... }

into a two-level hash. ex.

    { :controller => {"home" => ...} }

This avoids array allocations in exchange for more hash allocations, I
expect this to work out for the better as there is a ton of duplication
of the key names (specifically :controller and :action in normal apps).
  • Loading branch information
jhawthorn committed Nov 7, 2023
1 parent a736bba commit 9f9321c
Showing 1 changed file with 49 additions and 7 deletions.
56 changes: 49 additions & 7 deletions actionpack/lib/action_dispatch/journey/formatter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,12 @@ def non_recursive(cache, options)

while queue.any?
c = queue.shift
routes.concat(c[:___routes]) if c.key?(:___routes)
routes.concat(c.routes)

options.each do |pair|
queue << c[pair] if c.key?(pair)
options.each do |k, v|
if n = c.get(k, v)
queue << n
end
end
end

Expand Down Expand Up @@ -191,14 +193,54 @@ def missing_keys(route, parts)
missing_keys
end

class CacheNode
attr_reader :routes

EMPTY_ARRAY = [].freeze
EMPTY_HASH = {}.freeze

def initialize
@routes = nil
@children = nil
end

def finalize
@routes ||= EMPTY_ARRAY
@routes.freeze
@children ||= EMPTY_HASH
@children.each_value do |hash|
hash.each_value(&:finalize)
hash.freeze
end
@children.freeze
end

def get(key, value)
hash = @children[key]
return unless hash
hash[value]
end

def add_route(route)
(@routes ||= []) << route
end

def get_or_build_child(key, value)
hash = (@children ||= {})
hash = (hash[key] ||= {})
hash[value] ||= CacheNode.new
end
end

def build_cache
root = { ___routes: [] }
root = CacheNode.new
routes.routes.each do |route|
leaf = route.required_defaults.inject(root) do |h, tuple|
h[tuple] ||= {}
leaf = route.required_defaults.inject(root) do |h, (k, v)|
h.get_or_build_child(k, v)
end
(leaf[:___routes] ||= []) << route
leaf.add_route(route)
end
root.finalize
root
end

Expand Down

0 comments on commit 9f9321c

Please sign in to comment.