mirror of
https://github.com/ruby/ruby.git
synced 2026-08-03 13:08:59 +08:00
[ruby/prism] Optimize ripper bounds
Basically a port of c45f781771 into ruby
It's quite effective at ~97% hit rate for me.
Speeds it up from ~6.77x slower to only 4.07x slower.
For the lexer `on_sp` it also gives a bit of an improvement:
1.04x slower to 1.10x faster
I guess the class may be universally useful but for now I just made it nodoc.
https://github.com/ruby/prism/commit/3ad9db38fe
This commit is contained in:
parent
b583dd6879
commit
0cbd210c3a
@ -23,6 +23,12 @@ module Prism
|
||||
# def self.[]: (Integer value) -> State
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# class LineAndColumnCache
|
||||
# def initialize: (Source source) -> void
|
||||
#
|
||||
# def line_and_column: (Integer byte_offset) -> [Integer, Integer]
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
|
||||
@ -837,6 +843,8 @@ module Prism
|
||||
prev_token_state = Translation::Ripper::Lexer::State[Translation::Ripper::EXPR_BEG]
|
||||
prev_token_end = bom ? 3 : 0
|
||||
|
||||
cache = Translation::Ripper::LineAndColumnCache.new(source)
|
||||
|
||||
tokens.each do |token|
|
||||
# Skip missing heredoc ends.
|
||||
next if token[1] == :on_heredoc_end && token[2] == ""
|
||||
@ -851,8 +859,7 @@ module Prism
|
||||
|
||||
if start_offset > prev_token_end
|
||||
sp_value = source.slice(prev_token_end, start_offset - prev_token_end)
|
||||
sp_line = source.line(prev_token_end)
|
||||
sp_column = source.column(prev_token_end)
|
||||
sp_line, sp_column = cache.line_and_column(prev_token_end)
|
||||
# Ripper reports columns on line 1 without counting the BOM
|
||||
sp_column -= 3 if sp_line == 1 && bom
|
||||
continuation_index = sp_value.byteindex("\\")
|
||||
|
||||
@ -225,9 +225,7 @@ module Prism
|
||||
freeze
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Binary search through the offsets to find the line number for the given
|
||||
# Binary search through the offsets to find the index for the given
|
||||
# byte offset.
|
||||
#--
|
||||
#: (Integer byte_offset) -> Integer
|
||||
|
||||
@ -445,6 +445,64 @@ module Prism
|
||||
autoload :SexpBuilder, "prism/translation/ripper/sexp"
|
||||
autoload :SexpBuilderPP, "prism/translation/ripper/sexp"
|
||||
|
||||
# Provides optimized access to line and column information.
|
||||
# Ripper bounds are mostly accessed in a linear fashion, so
|
||||
# we can try a linear scan first and fall back to binary search.
|
||||
class LineAndColumnCache # :nodoc:
|
||||
# How many should it look ahead/behind before falling back to binary searching.
|
||||
WINDOW = 8
|
||||
private_constant :WINDOW
|
||||
|
||||
#: (Source source) -> void
|
||||
def initialize(source)
|
||||
@source = source
|
||||
@offsets = source.offsets
|
||||
@hint = 0
|
||||
end
|
||||
|
||||
#: (Integer byte_offset) -> [Integer, Integer]
|
||||
def line_and_column(byte_offset)
|
||||
@hint = new_hint(byte_offset) || @source.find_line(byte_offset)
|
||||
return [@hint + @source.start_line, byte_offset - @offsets[@hint]]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def new_hint(byte_offset)
|
||||
if @offsets[@hint] <= byte_offset
|
||||
# Same line?
|
||||
if (@hint + 1 >= @offsets.size || @offsets[@hint + 1] > byte_offset)
|
||||
return @hint
|
||||
end
|
||||
|
||||
# Scan forwards
|
||||
limit = [@hint + WINDOW + 1, @offsets.size].min
|
||||
idx = @hint + 1
|
||||
while idx < limit
|
||||
if @offsets[idx] > byte_offset
|
||||
return idx - 1
|
||||
end
|
||||
if @offsets[idx] == byte_offset
|
||||
return idx
|
||||
end
|
||||
idx += 1
|
||||
end
|
||||
else
|
||||
# Scan backwards
|
||||
limit = @hint > WINDOW ? @hint - WINDOW : 0
|
||||
idx = @hint
|
||||
while idx >= limit + 1
|
||||
if @offsets[idx - 1] <= byte_offset
|
||||
return idx - 1
|
||||
end
|
||||
idx -= 1
|
||||
end
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
# :stopdoc:
|
||||
# This is not part of the public API but used by some gems.
|
||||
|
||||
@ -488,6 +546,7 @@ module Prism
|
||||
@lineno = lineno
|
||||
@column = 0
|
||||
@result = nil
|
||||
@line_and_column_cache = nil
|
||||
end
|
||||
|
||||
##########################################################################
|
||||
@ -4033,6 +4092,10 @@ module Prism
|
||||
@result ||= Prism.parse(source, partial_script: true, version: "current")
|
||||
end
|
||||
|
||||
def line_and_column_cache
|
||||
@line_and_column_cache ||= LineAndColumnCache.new(result.source)
|
||||
end
|
||||
|
||||
##########################################################################
|
||||
# Helpers
|
||||
##########################################################################
|
||||
@ -4133,12 +4196,8 @@ module Prism
|
||||
|
||||
# This method is responsible for updating lineno and column information
|
||||
# to reflect the current node.
|
||||
#
|
||||
# This method could be drastically improved with some caching on the start
|
||||
# of every line, but for now it's good enough.
|
||||
def bounds(location)
|
||||
@lineno = location.start_line
|
||||
@column = location.start_column
|
||||
@lineno, @column = line_and_column_cache.line_and_column(location.start_offset)
|
||||
end
|
||||
|
||||
# :startdoc:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user