From bdde16554cef76300b021be74055cc5333325e1f Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Mon, 19 Jan 2026 22:31:13 +0100 Subject: [PATCH] Optimize ripper translator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Creating state classes is pretty expensive. Since they are not modifiable, we can reuse them instead. Benchmark script: ```rb require "ripper" require "prism" require "benchmark/ips" codes = Dir["**/*.rb"].map { File.read(it) } Benchmark.ips do |x| x.config(time: 10) x.report("prism") { codes.each { Prism::Translation::Ripper.lex(it) } } x.report("ripper") { codes.each { Ripper.lex(it) } } x.compare! end ``` Before: ``` ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [x86_64-linux] Warming up -------------------------------------- prism 1.000 i/100ms ripper 1.000 i/100ms Calculating ------------------------------------- prism 0.293 (± 0.0%) i/s (3.42 s/i) - 3.000 in 10.248348s ripper 0.633 (± 0.0%) i/s (1.58 s/i) - 7.000 in 11.055687s Comparison: ripper: 0.6 i/s prism: 0.3 i/s - 2.16x slower ``` After ``` ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [x86_64-linux] Warming up -------------------------------------- prism 1.000 i/100ms ripper 1.000 i/100ms Calculating ------------------------------------- prism 0.486 (± 0.0%) i/s (2.06 s/i) - 5.000 in 10.280413s ripper 0.635 (± 0.0%) i/s (1.58 s/i) - 7.000 in 11.027169s Comparison: ripper: 0.6 i/s prism: 0.5 i/s - 1.31x slower ``` --- lib/prism/lex_compat.rb | 4 ++-- lib/prism/translation/ripper/lexer.rb | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/prism/lex_compat.rb b/lib/prism/lex_compat.rb index b7c54178ac..f7b9a0effc 100644 --- a/lib/prism/lex_compat.rb +++ b/lib/prism/lex_compat.rb @@ -667,7 +667,7 @@ def result event = RIPPER.fetch(token.type) value = token.value - lex_state = Translation::Ripper::Lexer::State.new(lex_state) + lex_state = Translation::Ripper::Lexer::State.cached(lex_state) token = case event @@ -734,7 +734,7 @@ def result counter += { on_embexpr_beg: -1, on_embexpr_end: 1 }[current_event] || 0 end - Translation::Ripper::Lexer::State.new(result_value[current_index][1]) + Translation::Ripper::Lexer::State.cached(result_value[current_index][1]) else previous_state end diff --git a/lib/prism/translation/ripper/lexer.rb b/lib/prism/translation/ripper/lexer.rb index bd40fb4c5a..bed863af08 100644 --- a/lib/prism/translation/ripper/lexer.rb +++ b/lib/prism/translation/ripper/lexer.rb @@ -38,6 +38,13 @@ def |(i) self.class.new(to_int | i) end def allbits?(i) to_int.allbits?(i) end def anybits?(i) to_int.anybits?(i) end def nobits?(i) to_int.nobits?(i) end + + # Instances are frozen and there are only a handful of them so we cache them here. + STATES = Hash.new { |h,k| h[k] = State.new(k) } + + def self.cached(i) + STATES[i] + end end class Elem @@ -47,7 +54,7 @@ def initialize(pos, event, tok, state, message = nil) @pos = pos @event = event @tok = tok - @state = State.new(state) + @state = State.cached(state) @message = message end