Skip to content

Commit f7bc28d

Browse files
Earlopainmatzbot
authored andcommitted
[ruby/prism] Further optimize ripper translator by not using delegate
Using it seems pretty bad for performance: ```rb require "benchmark/ips" require "prism" require "ripper" codes = Dir["**/*.rb"].map { File.read(it) } Benchmark.ips do |x| 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 ruby/prism@553f1675f3) +PRISM [x86_64-linux] Warming up -------------------------------------- prism 1.000 i/100ms ripper 1.000 i/100ms Calculating ------------------------------------- prism 0.319 (± 0.0%) i/s (3.14 s/i) - 2.000 in 6.276154s ripper 0.647 (± 0.0%) i/s (1.54 s/i) - 4.000 in 6.182662s Comparison: ripper: 0.6 i/s prism: 0.3 i/s - 2.03x slower # After ruby 4.0.0 (2025-12-25 revision ruby/prism@553f1675f3) +PRISM [x86_64-linux] Warming up -------------------------------------- prism 1.000 i/100ms ripper 1.000 i/100ms Calculating ------------------------------------- prism 0.482 (± 0.0%) i/s (2.08 s/i) - 3.000 in 6.225603s ripper 0.645 (± 0.0%) i/s (1.55 s/i) - 4.000 in 6.205636s Comparison: ripper: 0.6 i/s prism: 0.5 i/s - 1.34x slower ``` `vernier` tells me it does `method_missing` even for explicitly defined methods like `location`. ruby/prism@2ea81398cc
1 parent 1de6133 commit f7bc28d

File tree

1 file changed

+22
-8
lines changed

1 file changed

+22
-8
lines changed

lib/prism/lex_compat.rb

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
# frozen_string_literal: true
22
# :markup: markdown
33

4-
require "delegate"
5-
64
module Prism
75
# This class is responsible for lexing the source using prism and then
86
# converting those tokens to be compatible with Ripper. In the vast majority
@@ -201,27 +199,43 @@ def deconstruct_keys(keys)
201199
# When we produce tokens, we produce the same arrays that Ripper does.
202200
# However, we add a couple of convenience methods onto them to make them a
203201
# little easier to work with. We delegate all other methods to the array.
204-
class Token < SimpleDelegator
205-
# @dynamic initialize, each, []
202+
class Token < BasicObject
203+
# Create a new token object with the given ripper-compatible array.
204+
def initialize(array)
205+
@array = array
206+
end
206207

207208
# The location of the token in the source.
208209
def location
209-
self[0]
210+
@array[0]
210211
end
211212

212213
# The type of the token.
213214
def event
214-
self[1]
215+
@array[1]
215216
end
216217

217218
# The slice of the source that this token represents.
218219
def value
219-
self[2]
220+
@array[2]
220221
end
221222

222223
# The state of the lexer when this token was produced.
223224
def state
224-
self[3]
225+
@array[3]
226+
end
227+
228+
# We want to pretend that this is just an Array.
229+
def ==(other) # :nodoc:
230+
@array == other
231+
end
232+
233+
def respond_to_missing?(name, include_private = false) # :nodoc:
234+
@array.respond_to?(name, include_private)
235+
end
236+
237+
def method_missing(name, ...) # :nodoc:
238+
@array.send(name, ...)
225239
end
226240
end
227241

0 commit comments

Comments
 (0)