Skip to content

Commit

Permalink
Performance improvement
Browse files Browse the repository at this point in the history
This change improves performance about 30% by skipping
object comparison of `Lrama::Lexer::Token`.

`Lrama::Grammar::Symbol` is an unique object
then it's ok to use comparison methods inherited from `Object`.

## Execution time

### Before

```
$ time bundle exec exe/lrama --trace=time -o parse.tmp.c --header=parse.tmp.h tmp/parse.tmp.y
parse    0.71535 s
compute_lr0_states    0.30997 s
compute_direct_read_sets    0.03551 s
compute_reads_relation    0.00628 s
compute_read_sets    0.01906 s
compute_includes_relation    0.38075 s
compute_lookback_relation    0.76338 s
compute_follow_sets    0.05411 s
compute_look_ahead_sets    0.55136 s
compute_conflicts    0.03092 s
compute_default_reduction    0.00360 s
compute_yydefact    0.04675 s
compute_yydefgoto    0.01515 s
sort_actions    0.00385 s
compute_packed_table    0.30613 s
render    0.06041 s
bundle exec exe/lrama --trace=time -o parse.tmp.c --header=parse.tmp.h   3.25s user 0.27s system 99% cpu 3.547 total
```

### After

```
$ time bundle exec exe/lrama --trace=time -o parse.tmp.c --header=parse.tmp.h tmp/parse.tmp.y
parse    0.71531 s
compute_lr0_states    0.25773 s
compute_direct_read_sets    0.03871 s
compute_reads_relation    0.00700 s
compute_read_sets    0.01984 s
compute_includes_relation    0.12310 s
compute_lookback_relation    0.22113 s
compute_follow_sets    0.05297 s
compute_look_ahead_sets    0.54991 s
compute_conflicts    0.00445 s
compute_default_reduction    0.00185 s
compute_yydefact    0.04470 s
compute_yydefgoto    0.00451 s
sort_actions    0.00378 s
compute_packed_table    0.30478 s
render    0.06135 s
bundle exec exe/lrama --trace=time -o parse.tmp.c --header=parse.tmp.h   2.36s user 0.27s system 99% cpu 2.660 total
```

## stackprof

### Before

```
$ stackprof tmp/stackprof-cpu-myapp_before.dump --text --limit 10
==================================
  Mode: cpu(1000)
  Samples: 1749 (0.00% miss rate)
  GC: 710 (40.59%)
==================================
     TOTAL    (pct)     SAMPLES    (pct)     FRAME
       560  (32.0%)         560  (32.0%)     (sweeping)
       203  (11.6%)         203  (11.6%)     Lrama::Lexer#lex_c_code
        79   (4.5%)          79   (4.5%)     (marking)
        74   (4.2%)          74   (4.2%)     Lrama::Lexer::Token#==
       130   (7.4%)          71   (4.1%)     Struct#==
       710  (40.6%)          71   (4.1%)     (garbage collection)
       119   (6.8%)          56   (3.2%)     Lrama::States#setup_state
        69   (3.9%)          46   (2.6%)     Lrama::Context#compute_packed_table
        47   (2.7%)          46   (2.6%)     Lrama::Lexer#lex_token
       730  (41.7%)          40   (2.3%)     Array#each
```

### After

```
stackprof tmp/stackprof-cpu-myapp.dump --text --limit 10
==================================
  Mode: cpu(1000)
  Samples: 1506 (0.00% miss rate)
  GC: 713 (47.34%)
==================================
     TOTAL    (pct)     SAMPLES    (pct)     FRAME
       570  (37.8%)         570  (37.8%)     (sweeping)
       208  (13.8%)         206  (13.7%)     Lrama::Lexer#lex_c_code
        79   (5.2%)          79   (5.2%)     (marking)
       713  (47.3%)          64   (4.2%)     (garbage collection)
        74   (4.9%)          57   (3.8%)     Lrama::Context#compute_packed_table
        98   (6.5%)          47   (3.1%)     Lrama::States#setup_state
        44   (2.9%)          42   (2.8%)     Lrama::Lexer#lex_token
        67   (4.4%)          39   (2.6%)     Lrama::States#compute_includes_relation
        58   (3.9%)          39   (2.6%)     Lrama::State#transition
       500  (33.2%)          39   (2.6%)     Array#each
```
  • Loading branch information
yui-knk committed Nov 26, 2023
1 parent b027362 commit 67693c3
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 20 deletions.
17 changes: 15 additions & 2 deletions lib/lrama/grammar/symbol.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,23 @@

module Lrama
class Grammar
class Symbol < Struct.new(:id, :alias_name, :number, :tag, :term, :token_id, :nullable, :precedence, :printer, :error_token, keyword_init: true)
attr_accessor :first_set, :first_set_bitmap
class Symbol
attr_accessor :id, :alias_name, :tag, :number, :token_id, :nullable, :precedence, :printer, :error_token, :first_set, :first_set_bitmap
attr_reader :term
attr_writer :eof_symbol, :error_symbol, :undef_symbol, :accept_symbol

def initialize(id:, alias_name: nil, number: nil, tag: nil, term:, token_id: nil, nullable: nil, precedence: nil, printer: nil)
@id = id
@alias_name = alias_name
@number = number
@tag = tag
@term = term
@token_id = token_id
@nullable = nullable
@precedence = precedence
@printer = printer
end

def term?
term
end
Expand Down
2 changes: 1 addition & 1 deletion spec/lrama/grammar/rule_builder_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
describe "@user_code" do
let(:location) { Lrama::Lexer::Location.new(first_line: 1, first_column: 0, last_line: 1, last_column: 4) }
let(:token_1) { Lrama::Lexer::Token::UserCode.new(s_value: "code 1", location: location) }
let(:sym) { Lrama::Grammar::Symbol.new(id: Lrama::Lexer::Token::Ident.new(s_value: "tPLUS")) }
let(:sym) { Lrama::Grammar::Symbol.new(id: Lrama::Lexer::Token::Ident.new(s_value: "tPLUS"), term: true) }

context "@user_code is not nil" do
it "sets @user_code to be nil and add previous user_code to rhs" do
Expand Down
8 changes: 4 additions & 4 deletions spec/lrama/grammar/symbol_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
describe "#enum_name" do
describe "symbol is accept_symbol" do
it "returns 'YYSYMBOL_YYACCEPT'" do
sym = described_class.new(id: token_class::Ident.new(s_value: "$accept"))
sym = described_class.new(id: token_class::Ident.new(s_value: "$accept"), term: false)
sym.accept_symbol = true

expect(sym.enum_name).to eq("YYSYMBOL_YYACCEPT")
Expand All @@ -13,7 +13,7 @@

describe "symbol is eof_symbol" do
it "returns 'YYSYMBOL_YYEOF'" do
sym = described_class.new(id: token_class::Ident.new(s_value: "YYEOF"), alias_name: "\"end of file\"", token_id: 0)
sym = described_class.new(id: token_class::Ident.new(s_value: "YYEOF"), alias_name: "\"end of file\"", token_id: 0, term: true)
sym.number = 0
sym.eof_symbol = true

Expand Down Expand Up @@ -57,7 +57,7 @@
describe "#comment" do
describe "symbol is accept_symbol" do
it "returns s_value" do
sym = described_class.new(id: token_class::Ident.new(s_value: "$accept"))
sym = described_class.new(id: token_class::Ident.new(s_value: "$accept"), term: false)
sym.accept_symbol = true

expect(sym.comment).to eq("$accept")
Expand All @@ -66,7 +66,7 @@

describe "symbol is eof_symbol" do
it "returns alias_name" do
sym = described_class.new(id: token_class::Ident.new(s_value: "YYEOF"), alias_name: "\"end of file\"", token_id: 0)
sym = described_class.new(id: token_class::Ident.new(s_value: "YYEOF"), alias_name: "\"end of file\"", token_id: 0, term: true)
sym.number = 0
sym.eof_symbol = true

Expand Down
26 changes: 13 additions & 13 deletions spec/lrama/parser_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
expect(grammar.lex_param).to eq("struct lex_params *p")
expect(grammar.parse_param).to eq("struct parse_params *p")
expect(grammar.initial_action).to eq(Code::InitialActionCode.new(type: :initial_action, token_code: T::UserCode.new(s_value: "\n initial_action_func(@$);\n")))
expect(grammar.symbols.sort_by(&:number)).to eq([
expect(grammar.symbols.sort_by(&:number)).to match_symbols([
Sym.new(id: T::Ident.new(s_value: "EOI"), alias_name: "\"EOI\"", number: 0, tag: nil, term: true, token_id: 0, nullable: false, precedence: nil, printer: nil),
Sym.new(id: T::Ident.new(s_value: "YYerror"), alias_name: "error", number: 1, tag: nil, term: true, token_id: 256, nullable: false, precedence: nil, printer: nil),
Sym.new(id: T::Ident.new(s_value: "YYUNDEF"), alias_name: "\"invalid token\"", number: 2, tag: nil, term: true, token_id: 257, nullable: false, precedence: nil, printer: nil),
Expand Down Expand Up @@ -417,7 +417,7 @@
y = File.read(fixture_path(path))
grammar = Lrama::Parser.new(y, path).parse

expect(grammar.nterms.sort_by(&:number)).to eq([
expect(grammar.nterms.sort_by(&:number)).to match_symbols([
Sym.new(id: T::Ident.new(s_value: "$accept"), alias_name: nil, number: 6, tag: nil, term: false, token_id: 0, nullable: false),
Sym.new(id: T::Ident.new(s_value: "program"), alias_name: nil, number: 7, tag: nil, term: false, token_id: 1, nullable: true),
Sym.new(id: T::Ident.new(s_value: "stmt"), alias_name: nil, number: 8, tag: nil, term: false, token_id: 2, nullable: true),
Expand Down Expand Up @@ -563,7 +563,7 @@
y = File.read(fixture_path(path))
grammar = Lrama::Parser.new(y, path).parse

expect(grammar.nterms.sort_by(&:number)).to eq([
expect(grammar.nterms.sort_by(&:number)).to match_symbols([
Sym.new(id: T::Ident.new(s_value: "$accept"), alias_name: nil, number: 5, tag: nil, term: false, token_id: 0, nullable: false),
Sym.new(id: T::Ident.new(s_value: "program"), alias_name: nil, number: 6, tag: nil, term: false, token_id: 1, nullable: true),
Sym.new(id: T::Ident.new(s_value: "option_number"), alias_name: nil, number: 7, tag: nil, term: false, token_id: 2, nullable: true),
Expand Down Expand Up @@ -652,7 +652,7 @@
y.sub!('option(', 'option (')
grammar = Lrama::Parser.new(y, path).parse

expect(grammar.nterms.sort_by(&:number)).to eq([
expect(grammar.nterms.sort_by(&:number)).to match_symbols([
Sym.new(id: T::Ident.new(s_value: "$accept"), alias_name: nil, number: 5, tag: nil, term: false, token_id: 0, nullable: false),
Sym.new(id: T::Ident.new(s_value: "program"), alias_name: nil, number: 6, tag: nil, term: false, token_id: 1, nullable: true),
Sym.new(id: T::Ident.new(s_value: "option_number"), alias_name: nil, number: 7, tag: nil, term: false, token_id: 2, nullable: true),
Expand Down Expand Up @@ -740,7 +740,7 @@
y = File.read(fixture_path(path))
grammar = Lrama::Parser.new(y, path).parse

expect(grammar.nterms.sort_by(&:number)).to eq([
expect(grammar.nterms.sort_by(&:number)).to match_symbols([
Sym.new(id: T::Ident.new(s_value: "$accept"), alias_name: nil, number: 6, tag: nil, term: false, token_id: 0, nullable: false),
Sym.new(id: T::Ident.new(s_value: "program"), alias_name: nil, number: 7, tag: nil, term: false, token_id: 1, nullable: false),
Sym.new(id: T::Ident.new(s_value: "option_bar"), alias_name: nil, number: 8, tag: nil, term: false, token_id: 2, nullable: true),
Expand Down Expand Up @@ -800,7 +800,7 @@
y = File.read(fixture_path(path))
grammar = Lrama::Parser.new(y, path).parse

expect(grammar.nterms.sort_by(&:number)).to eq([
expect(grammar.nterms.sort_by(&:number)).to match_symbols([
Sym.new(id: T::Ident.new(s_value: "$accept"), alias_name: nil, number: 5, tag: nil, term: false, token_id: 0, nullable: false),
Sym.new(id: T::Ident.new(s_value: "program"), alias_name: nil, number: 6, tag: nil, term: false, token_id: 1, nullable: false),
Sym.new(id: T::Ident.new(s_value: "nonempty_list_number"), alias_name: nil, number: 7, tag: nil, term: false, token_id: 2, nullable: false),
Expand Down Expand Up @@ -894,7 +894,7 @@
y = File.read(fixture_path(path))
grammar = Lrama::Parser.new(y, path).parse

expect(grammar.nterms.sort_by(&:number)).to eq([
expect(grammar.nterms.sort_by(&:number)).to match_symbols([
Sym.new(id: T::Ident.new(s_value: "$accept"), alias_name: nil, number: 5, tag: nil, term: false, token_id: 0, nullable: false),
Sym.new(id: T::Ident.new(s_value: "program"), alias_name: nil, number: 6, tag: nil, term: false, token_id: 1, nullable: true),
Sym.new(id: T::Ident.new(s_value: "list_number"), alias_name: nil, number: 7, tag: nil, term: false, token_id: 2, nullable: true),
Expand Down Expand Up @@ -984,7 +984,7 @@
y = File.read(fixture_path(path))
grammar = Lrama::Parser.new(y, path).parse

expect(grammar.nterms.sort_by(&:number)).to eq([
expect(grammar.nterms.sort_by(&:number)).to match_symbols([
Sym.new(id: T::Ident.new(s_value: "$accept"), alias_name: nil, number: 5, tag: nil, term: false, token_id: 0, nullable: false),
Sym.new(id: T::Ident.new(s_value: "program"), alias_name: nil, number: 6, tag: nil, term: false, token_id: 1, nullable: false),
Sym.new(id: T::Ident.new(s_value: "separated_nonempty_list_number"), alias_name: nil, number: 7, tag: nil, term: false, token_id: 2, nullable: false),
Expand Down Expand Up @@ -1046,7 +1046,7 @@
y = File.read(fixture_path(path))
grammar = Lrama::Parser.new(y, path).parse

expect(grammar.nterms.sort_by(&:number)).to eq([
expect(grammar.nterms.sort_by(&:number)).to match_symbols([
Sym.new(id: T::Ident.new(s_value: "$accept"), alias_name: nil, number: 5, tag: nil, term: false, token_id: 0, nullable: false),
Sym.new(id: T::Ident.new(s_value: "program"), alias_name: nil, number: 6, tag: nil, term: false, token_id: 1, nullable: true),
Sym.new(id: T::Ident.new(s_value: "separated_list_number"), alias_name: nil, number: 7, tag: nil, term: false, token_id: 2, nullable: true),
Expand Down Expand Up @@ -1201,7 +1201,7 @@ class : keyword_class tSTRING keyword_end { code 1 }
INPUT
grammar = Lrama::Parser.new(y, "parse.y").parse

expect(grammar.terms.sort_by(&:number)).to eq([
expect(grammar.terms.sort_by(&:number)).to match_symbols([
Sym.new(id: T::Ident.new(s_value: "EOI"), alias_name: "\"EOI\"", number: 0, tag: nil, term: true, token_id: 0, nullable: false, precedence: nil),
Sym.new(id: T::Ident.new(s_value: "YYerror"), alias_name: "error", number: 1, tag: nil, term: true, token_id: 256, nullable: false, precedence: nil),
Sym.new(id: T::Ident.new(s_value: "YYUNDEF"), alias_name: "\"invalid token\"", number: 2, tag: nil, term: true, token_id: 257, nullable: false, precedence: nil),
Expand Down Expand Up @@ -1258,7 +1258,7 @@ class : keyword_class { code 1 } tSTRING { code 2 } keyword_end { code 3 }
INPUT
grammar = Lrama::Parser.new(y, "parse.y").parse

expect(grammar.nterms.sort_by(&:number)).to eq([
expect(grammar.nterms.sort_by(&:number)).to match_symbols([
Sym.new(id: T::Ident.new(s_value: "$accept"), alias_name: nil, number: 11, tag: nil, term: false, token_id: 0, nullable: false),
Sym.new(id: T::Ident.new(s_value: "program"), alias_name: nil, number: 12, tag: nil, term: false, token_id: 1, nullable: false),
Sym.new(id: T::Ident.new(s_value: "class"), alias_name: nil, number: 13, tag: T::Tag.new(s_value: "<i>"), term: false, token_id: 2, nullable: false),
Expand Down Expand Up @@ -1494,7 +1494,7 @@ class : keyword_class tSTRING keyword_end { code 1 }
INPUT
grammar = Lrama::Parser.new(y, "parse.y").parse

expect(grammar.terms.sort_by(&:number)).to eq([
expect(grammar.terms.sort_by(&:number)).to match_symbols([
Sym.new(id: T::Ident.new(s_value: "EOI"), alias_name: "\"EOI\"", number: 0, tag: nil, term: true, token_id: 0, nullable: false),
Sym.new(id: T::Ident.new(s_value: "YYerror"), alias_name: "error", number: 1, tag: nil, term: true, token_id: 256, nullable: false),
Sym.new(id: T::Ident.new(s_value: "YYUNDEF"), alias_name: "\"invalid token\"", number: 2, tag: nil, term: true, token_id: 257, nullable: false),
Expand Down Expand Up @@ -1543,7 +1543,7 @@ class : keyword_class tSTRING keyword_end { code 1 }
INPUT
grammar = Lrama::Parser.new(y, "parse.y").parse

expect(grammar.terms.sort_by(&:number)).to eq([
expect(grammar.terms.sort_by(&:number)).to match_symbols([
Sym.new(id: T::Ident.new(s_value: "EOI"), alias_name: "\"EOI\"", number: 0, tag: nil, term: true, token_id: 0, nullable: false, precedence: nil),
Sym.new(id: T::Ident.new(s_value: "YYerror"), alias_name: "error", number: 1, tag: nil, term: true, token_id: 256, nullable: false, precedence: nil),
Sym.new(id: T::Ident.new(s_value: "YYUNDEF"), alias_name: "\"invalid token\"", number: 2, tag: nil, term: true, token_id: 257, nullable: false, precedence: nil),
Expand Down
108 changes: 108 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,113 @@ def windows?
end
end

module LramaCustomMatchers
class SymbolMatcher
attr_reader :expected, :target

def initialize(expected)
@expected = expected
@_failure_message = nil
end

def matches?(target)
@target = target

if !@expected.is_a?(Lrama::Grammar::Symbol)
@_failure_message = "expected #{@expected.inspect} to be Lrama::Grammar::Symbol"
return false
end

if !@target.is_a?(Lrama::Grammar::Symbol)
@_failure_message = "expected #{@target.inspect} to be Lrama::Grammar::Symbol"
return false
end

@expected.id == @target.id &&
@expected.alias_name == @target.alias_name &&
@expected.number == @target.number &&
@expected.tag == @target.tag &&
@expected.term == @target.term &&
@expected.token_id == @target.token_id &&
@expected.nullable == @target.nullable &&
@expected.precedence == @target.precedence &&
@expected.printer == @target.printer &&
@expected.error_token == @target.error_token
end

def failure_message
return @_failure_message if @_failure_message

"expected #{@target.inspect} to match with #{@expected.inspect}"
end

def failure_message_when_negated
return @_failure_message if @_failure_message

"expected #{@target.inspect} not to match with #{@expected.inspect}"
end
end

class SymbolsMatcher
attr_reader :expected, :target

def initialize(expected)
@expected = expected
@_failure_message = nil
end

def matches?(target)
@target = target

if !@expected.is_a?(Array)
@_failure_message = "expected #{@expected.inspect} to be Array"
return false
end

if !@target.is_a?(Array)
@_failure_message = "expected #{@target.inspect} to be Array"
return false
end

if @expected.count != @target.count
@_failure_message = "expected the number of array to be same (#{@expected.count} != #{@target.count})"
return false
end

@not_matched = []

@expected.zip(@target).each do |expected, actual|
matcher = SymbolMatcher.new(expected)
unless matcher.matches?(actual)
@not_matched << matcher
end
end

@not_matched.empty?
end

def failure_message
return @_failure_message if @_failure_message

@not_matched.map(&:failure_message).join("\n")
end

def failure_message_when_negated
return @_failure_message if @_failure_message

@not_matched.map(&:failure_message_when_negated).join("\n")
end
end

def match_symbol(expected)
SymbolMatcher.new(expected)
end

def match_symbols(expected)
SymbolsMatcher.new(expected)
end
end

RSpec.configure do |config|
# Disable RSpec exposing methods globally on `Module` and `main`
config.disable_monkey_patching!
Expand All @@ -45,6 +152,7 @@ def windows?
end

config.include(RspecHelper)
config.include(LramaCustomMatchers)

# Allow to limit the run of the specs
# NOTE: Please do not commit the filter option.
Expand Down

0 comments on commit 67693c3

Please sign in to comment.