-
Notifications
You must be signed in to change notification settings - Fork 177
Optimize ruby visitor #3826
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Optimize ruby visitor #3826
Conversation
`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 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 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 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 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
| def child_nodes: () -> Array[Prism::node?] | ||
| def comment_targets: () -> Array[Prism::node | Location] | ||
| def compact_child_nodes: () -> Array[Prism::node] | ||
| def each_child_node: () { (Prism::node) -> void } -> void | () -> Enumerator[Prism::node] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unsure if this is correct. I'm also missing sorbet signatures which I don't know how to write for this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks right to me, and it would have failed to typecheck otherwise.
| end | ||
| # def compact_child_nodes: () -> Array[Node] | ||
| def compact_child_nodes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can just be implemented as each_child_node.to_a but performance is not so great
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, you're right to keep this the same.
#3808 (comment)
This also came up someplace else before but I don't remember where. I agree this makes sense. cc @eregon
compact_child_nodesallocates an array. We can skip that step by simply yielding the nodes.Benchmark for visiting the rails codebase:
Before:
After:
~21% faster on the interpreter, ~56% with YJIT