1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00
rails--rails/actionpack/test/journey/nodes/ast_test.rb

92 lines
2.4 KiB
Ruby
Raw Normal View History

Introduce Journey::Ast to avoid extra ast walks This commit introduces a new `Journey::Ast` class that wraps the root node of an ast. The purpose of this class is to reduce the number of walks through the ast by taking a single pass through each node and caching values for later use. To avoid retaining additional memory, we clear out these ast objects after eager loading. Benefits --- * Performance improvements (see benchmarks below) * Keeps various ast manipulations together in a single class, rather than scattered throughout * Adding some names to things will hopefully make this code a little easier to follow for future readers Benchmarks --- We benchmarked loading a real routes file with > 3500 routes. master was at a9336a67b0 when we ran these. Note that these benchmarks also include a few small changes that we didn't include in this commit, but that we will follow up with after this gets merged - these additional changes do not change the benchmarks significantly. Time: ``` master - 0.798 ± 0.024 (500 runs) this branch - 0.695 ± 0.029 (500 runs) ``` Allocations: ``` master - 980149 total allocated objects this branch - 931357 total allocated objects ``` Stackprof: Seeing `ActionDispatch::Journey::Visitors::Each#visit` more frequently on the stack is what led us down this path in the first place. These changes seem to have done the trick. ``` master: TOTAL (pct) SAMPLES (pct) FRAME 52 (0.5%) 52 (0.5%) ActionDispatch::Journey::Nodes::Node#symbol? 58 (0.5%) 45 (0.4%) ActionDispatch::Journey::Scanner#scan 45 (0.4%) 45 (0.4%) ActionDispatch::Journey::Nodes::Cat#type 43 (0.4%) 43 (0.4%) ActionDispatch::Journey::Visitors::FunctionalVisitor#terminal 303 (2.7%) 43 (0.4%) ActionDispatch::Journey::Visitors::Each#visit 69 (0.6%) 40 (0.4%) ActionDispatch::Routing::Mapper::Scope#each this commit: TOTAL (pct) SAMPLES (pct) FRAME 82 (0.6%) 42 (0.3%) ActionDispatch::Journey::Scanner#next_token 31 (0.2%) 31 (0.2%) ActionDispatch::Journey::Nodes::Node#symbol? 30 (0.2%) 30 (0.2%) ActionDispatch::Journey::Nodes::Node#initialize ``` See also the benchmark script in https://github.com/rails/rails/pull/39935#issuecomment-887791294 Co-authored-by: Eric Milford <ericmilford@gmail.com>
2020-07-27 12:13:15 -04:00
# frozen_string_literal: true
require "abstract_unit"
module ActionDispatch
module Journey
module Nodes
class TestAst < ActiveSupport::TestCase
def test_ast_sets_regular_expressions
requirements = { name: /(tender|love)/, value: /./ }
path = "/page/:name/:value"
tree = Journey::Parser.new.parse(path)
ast = Ast.new(tree, true)
ast.requirements = requirements
nodes = ast.root.grep(Nodes::Symbol)
assert_equal 2, nodes.length
nodes.each do |node|
assert_equal requirements[node.to_sym], node.regexp
end
end
def test_sets_memo_for_terminal_nodes
route = Object.new
tree = Journey::Parser.new.parse("/path")
ast = Ast.new(tree, true)
ast.route = route
nodes = ast.root.grep(Nodes::Terminal)
nodes.each do |node|
assert_equal route, node.memo
end
end
def test_contains_glob
tree = Journey::Parser.new.parse("/*glob")
ast = Ast.new(tree, true)
assert_predicate ast, :glob?
end
def test_does_not_contain_glob
tree = Journey::Parser.new.parse("/")
ast = Ast.new(tree, true)
assert_not_predicate ast, :glob?
end
def test_names
tree = Journey::Parser.new.parse("/:path/:symbol")
ast = Ast.new(tree, true)
assert_equal ["path", "symbol"], ast.names
end
def test_path_params
tree = Journey::Parser.new.parse("/:path/:symbol")
ast = Ast.new(tree, true)
assert_equal [:path, :symbol], ast.path_params
end
def test_wildcard_options_when_formatted
tree = Journey::Parser.new.parse("/*glob")
ast = Ast.new(tree, true)
wildcard_options = ast.wildcard_options
assert_equal %r{.+?}m, wildcard_options[:glob]
Introduce Journey::Ast to avoid extra ast walks This commit introduces a new `Journey::Ast` class that wraps the root node of an ast. The purpose of this class is to reduce the number of walks through the ast by taking a single pass through each node and caching values for later use. To avoid retaining additional memory, we clear out these ast objects after eager loading. Benefits --- * Performance improvements (see benchmarks below) * Keeps various ast manipulations together in a single class, rather than scattered throughout * Adding some names to things will hopefully make this code a little easier to follow for future readers Benchmarks --- We benchmarked loading a real routes file with > 3500 routes. master was at a9336a67b0 when we ran these. Note that these benchmarks also include a few small changes that we didn't include in this commit, but that we will follow up with after this gets merged - these additional changes do not change the benchmarks significantly. Time: ``` master - 0.798 ± 0.024 (500 runs) this branch - 0.695 ± 0.029 (500 runs) ``` Allocations: ``` master - 980149 total allocated objects this branch - 931357 total allocated objects ``` Stackprof: Seeing `ActionDispatch::Journey::Visitors::Each#visit` more frequently on the stack is what led us down this path in the first place. These changes seem to have done the trick. ``` master: TOTAL (pct) SAMPLES (pct) FRAME 52 (0.5%) 52 (0.5%) ActionDispatch::Journey::Nodes::Node#symbol? 58 (0.5%) 45 (0.4%) ActionDispatch::Journey::Scanner#scan 45 (0.4%) 45 (0.4%) ActionDispatch::Journey::Nodes::Cat#type 43 (0.4%) 43 (0.4%) ActionDispatch::Journey::Visitors::FunctionalVisitor#terminal 303 (2.7%) 43 (0.4%) ActionDispatch::Journey::Visitors::Each#visit 69 (0.6%) 40 (0.4%) ActionDispatch::Routing::Mapper::Scope#each this commit: TOTAL (pct) SAMPLES (pct) FRAME 82 (0.6%) 42 (0.3%) ActionDispatch::Journey::Scanner#next_token 31 (0.2%) 31 (0.2%) ActionDispatch::Journey::Nodes::Node#symbol? 30 (0.2%) 30 (0.2%) ActionDispatch::Journey::Nodes::Node#initialize ``` See also the benchmark script in https://github.com/rails/rails/pull/39935#issuecomment-887791294 Co-authored-by: Eric Milford <ericmilford@gmail.com>
2020-07-27 12:13:15 -04:00
end
def test_wildcard_options_when_false
tree = Journey::Parser.new.parse("/*glob")
ast = Ast.new(tree, false)
wildcard_options = ast.wildcard_options
assert_nil wildcard_options[:glob]
end
def test_wildcard_options_when_nil
tree = Journey::Parser.new.parse("/*glob")
ast = Ast.new(tree, nil)
wildcard_options = ast.wildcard_options
assert_equal %r{.+?}m, wildcard_options[:glob]
Introduce Journey::Ast to avoid extra ast walks This commit introduces a new `Journey::Ast` class that wraps the root node of an ast. The purpose of this class is to reduce the number of walks through the ast by taking a single pass through each node and caching values for later use. To avoid retaining additional memory, we clear out these ast objects after eager loading. Benefits --- * Performance improvements (see benchmarks below) * Keeps various ast manipulations together in a single class, rather than scattered throughout * Adding some names to things will hopefully make this code a little easier to follow for future readers Benchmarks --- We benchmarked loading a real routes file with > 3500 routes. master was at a9336a67b0 when we ran these. Note that these benchmarks also include a few small changes that we didn't include in this commit, but that we will follow up with after this gets merged - these additional changes do not change the benchmarks significantly. Time: ``` master - 0.798 ± 0.024 (500 runs) this branch - 0.695 ± 0.029 (500 runs) ``` Allocations: ``` master - 980149 total allocated objects this branch - 931357 total allocated objects ``` Stackprof: Seeing `ActionDispatch::Journey::Visitors::Each#visit` more frequently on the stack is what led us down this path in the first place. These changes seem to have done the trick. ``` master: TOTAL (pct) SAMPLES (pct) FRAME 52 (0.5%) 52 (0.5%) ActionDispatch::Journey::Nodes::Node#symbol? 58 (0.5%) 45 (0.4%) ActionDispatch::Journey::Scanner#scan 45 (0.4%) 45 (0.4%) ActionDispatch::Journey::Nodes::Cat#type 43 (0.4%) 43 (0.4%) ActionDispatch::Journey::Visitors::FunctionalVisitor#terminal 303 (2.7%) 43 (0.4%) ActionDispatch::Journey::Visitors::Each#visit 69 (0.6%) 40 (0.4%) ActionDispatch::Routing::Mapper::Scope#each this commit: TOTAL (pct) SAMPLES (pct) FRAME 82 (0.6%) 42 (0.3%) ActionDispatch::Journey::Scanner#next_token 31 (0.2%) 31 (0.2%) ActionDispatch::Journey::Nodes::Node#symbol? 30 (0.2%) 30 (0.2%) ActionDispatch::Journey::Nodes::Node#initialize ``` See also the benchmark script in https://github.com/rails/rails/pull/39935#issuecomment-887791294 Co-authored-by: Eric Milford <ericmilford@gmail.com>
2020-07-27 12:13:15 -04:00
end
end
end
end
end