From 67693c36b45ff42d8df269ff63100eaa9a57f5a3 Mon Sep 17 00:00:00 2001 From: yui-knk Date: Sat, 25 Nov 2023 22:08:32 +0900 Subject: [PATCH] Performance improvement 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 ``` --- lib/lrama/grammar/symbol.rb | 17 +++- spec/lrama/grammar/rule_builder_spec.rb | 2 +- spec/lrama/grammar/symbol_spec.rb | 8 +- spec/lrama/parser_spec.rb | 26 +++--- spec/spec_helper.rb | 108 ++++++++++++++++++++++++ 5 files changed, 141 insertions(+), 20 deletions(-) diff --git a/lib/lrama/grammar/symbol.rb b/lib/lrama/grammar/symbol.rb index 39e5218d..26d861c3 100644 --- a/lib/lrama/grammar/symbol.rb +++ b/lib/lrama/grammar/symbol.rb @@ -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 diff --git a/spec/lrama/grammar/rule_builder_spec.rb b/spec/lrama/grammar/rule_builder_spec.rb index 0f184eca..cb31cdae 100644 --- a/spec/lrama/grammar/rule_builder_spec.rb +++ b/spec/lrama/grammar/rule_builder_spec.rb @@ -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 diff --git a/spec/lrama/grammar/symbol_spec.rb b/spec/lrama/grammar/symbol_spec.rb index 12708c91..9f3fc4bf 100644 --- a/spec/lrama/grammar/symbol_spec.rb +++ b/spec/lrama/grammar/symbol_spec.rb @@ -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") @@ -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 @@ -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") @@ -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 diff --git a/spec/lrama/parser_spec.rb b/spec/lrama/parser_spec.rb index cbfef263..0697e533 100644 --- a/spec/lrama/parser_spec.rb +++ b/spec/lrama/parser_spec.rb @@ -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), @@ -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), @@ -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), @@ -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), @@ -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), @@ -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), @@ -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), @@ -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), @@ -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), @@ -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), @@ -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: ""), term: false, token_id: 2, nullable: false), @@ -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), @@ -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), diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index c3cb60cb..645802bb 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -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! @@ -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.