Skip to content

Commit

Permalink
feat: Another 50%+ performance improvement
Browse files Browse the repository at this point in the history
Prior to this change, the runtime and generated code was using panic() and recover()
to perform error checking and reporting. This is extremely expensive and just not the
way to do it.

This change now uses goto, and explicit checking for error state after selected calls
into the runtime. This has greatly improved parser performance. Using the test code
provided by a recent performance issue report, the parse is now twoice as fast as the
issue raised was hoping for.

Signed-off-by: Jim.Idle <jimi@idle.ws>
  • Loading branch information
jimidle authored and parrt committed Mar 18, 2023
1 parent c0f6ece commit adbe946
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 103 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ s
[input]
b

[skip]
Go

Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,9 @@ AppendStr(a,b) ::= "<a> + <b>"
Concat(a,b) ::= "<a><b>"

AssertIsList(v) ::= <<
// A noddy range over the list will not compile if it is not getting a slice
// however, Go will not compile the generated code if the slice vs single value is wrong.
// Makes the Java based tests suite work though.
j1__ := make([]interface{}, len(<v>))
j2__ := <v>
for j3__ := range j2__ {
j1__[j3__] = j2__[j3__]
// Go will not compile this generated code if the slice vs single value is wrong.
for i := range localctx.(*ExpressionContext).GetArgs() {
_ = localctx.(*ExpressionContext).GetArgs()[i]
}
>>

Expand Down
3 changes: 3 additions & 0 deletions runtime/Go/antlr/v4/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,9 @@ func (p *BaseParser) Match(ttype int) Token {
p.Consume()
} else {
t = p.errHandler.RecoverInline(p)
if p.HasError() {
return nil
}
if p.BuildParseTrees && t.GetTokenIndex() == -1 {

// we must have conjured up a new token during single token
Expand Down
38 changes: 18 additions & 20 deletions runtime/Go/antlr/v4/parser_atn_simulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func (p *ParserATNSimulator) reset() {
}

//goland:noinspection GoBoolExpressions
func (p *ParserATNSimulator) AdaptivePredict(input TokenStream, decision int, outerContext ParserRuleContext) int {
func (p *ParserATNSimulator) AdaptivePredict(parser *BaseParser, input TokenStream, decision int, outerContext ParserRuleContext) int {
if ParserATNSimulatorDebug || ParserATNSimulatorTraceATNSim {
fmt.Println("adaptivePredict decision " + strconv.Itoa(decision) +
" exec LA(1)==" + p.getLookaheadName(input) +
Expand Down Expand Up @@ -147,7 +147,8 @@ func (p *ParserATNSimulator) AdaptivePredict(input TokenStream, decision int, ou
p.atn.stateMu.Unlock()
}

alt := p.execATN(dfa, s0, input, index, outerContext)
alt, re := p.execATN(dfa, s0, input, index, outerContext)
parser.SetError(re)
if ParserATNSimulatorDebug {
fmt.Println("DFA after predictATN: " + dfa.String(p.parser.GetLiteralNames(), nil))
}
Expand Down Expand Up @@ -189,7 +190,7 @@ func (p *ParserATNSimulator) AdaptivePredict(input TokenStream, decision int, ou
// - conflict + predicates
//
//goland:noinspection GoBoolExpressions
func (p *ParserATNSimulator) execATN(dfa *DFA, s0 *DFAState, input TokenStream, startIndex int, outerContext ParserRuleContext) int {
func (p *ParserATNSimulator) execATN(dfa *DFA, s0 *DFAState, input TokenStream, startIndex int, outerContext ParserRuleContext) (int, RecognitionException) {

if ParserATNSimulatorDebug || ParserATNSimulatorTraceATNSim {
fmt.Println("execATN decision " + strconv.Itoa(dfa.decision) +
Expand Down Expand Up @@ -223,11 +224,10 @@ func (p *ParserATNSimulator) execATN(dfa *DFA, s0 *DFAState, input TokenStream,
input.Seek(startIndex)
alt := p.getSynValidOrSemInvalidAltThatFinishedDecisionEntryRule(previousD.configs, outerContext)
if alt != ATNInvalidAltNumber {
return alt
return alt, nil
}
p.parser.SetError(e)
// TODO: JI - was panicing here.. how to drop out now?
return ATNInvalidAltNumber
return ATNInvalidAltNumber, e
}
if D.requiresFullContext && p.predictionMode != PredictionModeSLL {
// IF PREDS, MIGHT RESOLVE TO SINGLE ALT => SLL (or syntax error)
Expand All @@ -245,7 +245,7 @@ func (p *ParserATNSimulator) execATN(dfa *DFA, s0 *DFAState, input TokenStream,
if ParserATNSimulatorDebug {
fmt.Println("Full LL avoided")
}
return conflictingAlts.minValue()
return conflictingAlts.minValue(), nil
}
if conflictIndex != startIndex {
// restore the index so Reporting the fallback to full
Expand All @@ -259,26 +259,26 @@ func (p *ParserATNSimulator) execATN(dfa *DFA, s0 *DFAState, input TokenStream,
fullCtx := true
s0Closure := p.computeStartState(dfa.atnStartState, outerContext, fullCtx)
p.ReportAttemptingFullContext(dfa, conflictingAlts, D.configs, startIndex, input.Index())
alt := p.execATNWithFullContext(dfa, D, s0Closure, input, startIndex, outerContext)
return alt
alt, re := p.execATNWithFullContext(dfa, D, s0Closure, input, startIndex, outerContext)
return alt, re
}
if D.isAcceptState {
if D.predicates == nil {
return D.prediction
return D.prediction, nil
}
stopIndex := input.Index()
input.Seek(startIndex)
alts := p.evalSemanticContext(D.predicates, outerContext, true)

switch alts.length() {
case 0:
panic(p.noViableAlt(input, outerContext, D.configs, startIndex))
return ATNInvalidAltNumber, p.noViableAlt(input, outerContext, D.configs, startIndex)
case 1:
return alts.minValue()
return alts.minValue(), nil
default:
// Report ambiguity after predicate evaluation to make sure the correct set of ambig alts is Reported.
p.ReportAmbiguity(dfa, D, startIndex, stopIndex, false, alts, D.configs)
return alts.minValue()
return alts.minValue(), nil
}
}
previousD = D
Expand Down Expand Up @@ -394,7 +394,7 @@ func (p *ParserATNSimulator) predicateDFAState(dfaState *DFAState, decisionState
// comes back with reach.uniqueAlt set to a valid alt
//
//goland:noinspection GoBoolExpressions
func (p *ParserATNSimulator) execATNWithFullContext(dfa *DFA, D *DFAState, s0 ATNConfigSet, input TokenStream, startIndex int, outerContext ParserRuleContext) int {
func (p *ParserATNSimulator) execATNWithFullContext(dfa *DFA, D *DFAState, s0 ATNConfigSet, input TokenStream, startIndex int, outerContext ParserRuleContext) (int, RecognitionException) {

if ParserATNSimulatorDebug || ParserATNSimulatorTraceATNSim {
fmt.Println("execATNWithFullContext " + s0.String())
Expand All @@ -420,14 +420,12 @@ func (p *ParserATNSimulator) execATNWithFullContext(dfa *DFA, D *DFAState, s0 AT
// ATN states in SLL implies LL will also get nowhere.
// If conflict in states that dip out, choose min since we
// will get error no matter what.
e := p.noViableAlt(input, outerContext, previous, startIndex)
input.Seek(startIndex)
alt := p.getSynValidOrSemInvalidAltThatFinishedDecisionEntryRule(previous, outerContext)
if alt != ATNInvalidAltNumber {
return alt
return alt, nil
}

panic(e)
return alt, p.noViableAlt(input, outerContext, previous, startIndex)
}
altSubSets := PredictionModegetConflictingAltSubsets(reach)
if ParserATNSimulatorDebug {
Expand Down Expand Up @@ -469,7 +467,7 @@ func (p *ParserATNSimulator) execATNWithFullContext(dfa *DFA, D *DFAState, s0 AT
// not SLL.
if reach.GetUniqueAlt() != ATNInvalidAltNumber {
p.ReportContextSensitivity(dfa, predictedAlt, reach, startIndex, input.Index())
return predictedAlt
return predictedAlt, nil
}
// We do not check predicates here because we have checked them
// on-the-fly when doing full context prediction.
Expand Down Expand Up @@ -500,7 +498,7 @@ func (p *ParserATNSimulator) execATNWithFullContext(dfa *DFA, D *DFAState, s0 AT

p.ReportAmbiguity(dfa, D, startIndex, input.Index(), foundExactAmbig, reach.Alts(), reach)

return predictedAlt
return predictedAlt, nil
}

//goland:noinspection GoBoolExpressions
Expand Down
Loading

0 comments on commit adbe946

Please sign in to comment.