diff options
| author | Earlopain <14981592+Earlopain@users.noreply.github.com> | 2025-12-23 14:41:09 +0100 |
|---|---|---|
| committer | git <svn-admin@ruby-lang.org> | 2025-12-29 14:14:00 +0000 |
| commit | 65634d8df57ea1636efffb5f040fa31c156d307f (patch) | |
| tree | 5612711e853a946f923a55b33b274f955bee66f8 /prism | |
| parent | 14fbcf0e6ed37c4a0d15fd3f016778465f774f2c (diff) | |
[ruby/prism] Optimize ruby visitor
`compact_child_nodes` allocates an array. We can skip that step by simply yielding the nodes.
Benchmark for visiting the rails codebase:
```rb
require "prism"
require "benchmark/ips"
files = Dir.glob("../rails/**/*.rb")
results = files.map { Prism.parse_file(it) }
visitor = Prism::Visitor.new
Benchmark.ips do |x|
x.config(warmup: 3, time: 10)
x.report do
results.each do
visitor.visit(it.value)
end
end
end
RubyVM::YJIT.enable
Benchmark.ips do |x|
x.config(warmup: 3, time: 10)
x.report do
results.each do
visitor.visit(it.value)
end
end
end
```
Before:
```
ruby 3.4.8 (2025-12-17 revision https://github.com/ruby/prism/commit/995b59f666) +PRISM [x86_64-linux]
Warming up --------------------------------------
1.000 i/100ms
Calculating -------------------------------------
2.691 (± 0.0%) i/s (371.55 ms/i) - 27.000 in 10.089422s
ruby 3.4.8 (2025-12-17 revision https://github.com/ruby/prism/commit/995b59f666) +YJIT +PRISM [x86_64-linux]
Warming up --------------------------------------
1.000 i/100ms
Calculating -------------------------------------
7.278 (±13.7%) i/s (137.39 ms/i) - 70.000 in 10.071568s
```
After:
```
ruby 3.4.8 (2025-12-17 revision https://github.com/ruby/prism/commit/995b59f666) +PRISM [x86_64-linux]
Warming up --------------------------------------
1.000 i/100ms
Calculating -------------------------------------
3.429 (± 0.0%) i/s (291.65 ms/i) - 35.000 in 10.208580s
ruby 3.4.8 (2025-12-17 revision https://github.com/ruby/prism/commit/995b59f666) +YJIT +PRISM [x86_64-linux]
Warming up --------------------------------------
1.000 i/100ms
Calculating -------------------------------------
16.815 (± 0.0%) i/s (59.47 ms/i) - 169.000 in 10.054668s
```
~21% faster on the interpreter, ~56% with YJIT
https://github.com/ruby/prism/commit/bf631750cf
Diffstat (limited to 'prism')
| -rw-r--r-- | prism/templates/lib/prism/compiler.rb.erb | 4 | ||||
| -rw-r--r-- | prism/templates/lib/prism/node.rb.erb | 25 | ||||
| -rw-r--r-- | prism/templates/lib/prism/visitor.rb.erb | 4 |
3 files changed, 28 insertions, 5 deletions
diff --git a/prism/templates/lib/prism/compiler.rb.erb b/prism/templates/lib/prism/compiler.rb.erb index 9102025c20..66dbe666b9 100644 --- a/prism/templates/lib/prism/compiler.rb.erb +++ b/prism/templates/lib/prism/compiler.rb.erb @@ -29,14 +29,14 @@ module Prism # Visit the child nodes of the given node. def visit_child_nodes(node) - node.compact_child_nodes.map { |node| node.accept(self) } + node.each_child_node.map { |node| node.accept(self) } end <%- nodes.each_with_index do |node, index| -%> <%= "\n" if index != 0 -%> # Compile a <%= node.name %> node def visit_<%= node.human %>(node) - node.compact_child_nodes.map { |node| node.accept(self) } + node.each_child_node.map { |node| node.accept(self) } end <%- end -%> end diff --git a/prism/templates/lib/prism/node.rb.erb b/prism/templates/lib/prism/node.rb.erb index ceee2b0ffe..c97c029d3b 100644 --- a/prism/templates/lib/prism/node.rb.erb +++ b/prism/templates/lib/prism/node.rb.erb @@ -187,7 +187,7 @@ module Prism while (node = queue.shift) result << node - node.compact_child_nodes.each do |child_node| + node.each_child_node do |child_node| child_location = child_node.location start_line = child_location.start_line @@ -259,6 +259,13 @@ module Prism alias deconstruct child_nodes + # With a block given, yields each child node. Without a block, returns + # an enumerator that contains each child node. Excludes any `nil`s in + # the place of optional nodes that were not present. + def each_child_node + raise NoMethodError, "undefined method `each_child_node' for #{inspect}" + end + # Returns an array of child nodes, excluding any `nil`s in the place of # optional nodes that were not present. def compact_child_nodes @@ -335,6 +342,22 @@ module Prism }.compact.join(", ") %>] end + # def each_child_node: () { (Prism::node) -> void } -> void | () -> Enumerator[Prism::node] + def each_child_node + return to_enum(:each_child_node) unless block_given? + + <%- node.fields.each do |field| -%> + <%- case field -%> + <%- when Prism::Template::NodeField -%> + yield <%= field.name %> + <%- when Prism::Template::OptionalNodeField -%> + yield <%= field.name %> if <%= field.name %> + <%- when Prism::Template::NodeListField -%> + <%= field.name %>.each {|node| yield node } + <%- end -%> + <%- end -%> + end + # def compact_child_nodes: () -> Array[Node] def compact_child_nodes <%- if node.fields.any? { |field| field.is_a?(Prism::Template::OptionalNodeField) } -%> diff --git a/prism/templates/lib/prism/visitor.rb.erb b/prism/templates/lib/prism/visitor.rb.erb index b1a03c3f1a..76f907724f 100644 --- a/prism/templates/lib/prism/visitor.rb.erb +++ b/prism/templates/lib/prism/visitor.rb.erb @@ -20,7 +20,7 @@ module Prism # Visits the child nodes of `node` by calling `accept` on each one. def visit_child_nodes(node) # @type self: _Visitor - node.compact_child_nodes.each { |node| node.accept(self) } + node.each_child_node { |node| node.accept(self) } end end @@ -48,7 +48,7 @@ module Prism <%= "\n" if index != 0 -%> # Visit a <%= node.name %> node def visit_<%= node.human %>(node) - node.compact_child_nodes.each { |node| node.accept(self) } + node.each_child_node { |node| node.accept(self) } end <%- end -%> end |
