Repository navigation
perf: trim per-parse allocations that do no work - #2874
Merged
manticore-projects merged 4 commits intoOct 31, 2026
Merged
manticore-projects merged 4 commits into
manticore-projects merged 4 commits into
Conversation
`Condition()` created a deque for leading `NOT` and `!` prefixes on every call, though few conditions have one. An empty `ArrayDeque` allocates its 17-slot array up front, so parsing `performance.sql` allocated 585 KB per parse for deques that stayed empty. The deque is now created at the first `NOT` or `!`.
`KeyWord.from` runs for every `LIKE`-family condition the parser builds, and `String.replaceAll` compiled its `\s+` pattern on each call. Parsing `performance.sql` allocated 201 KB per parse for those compilations. The pattern is now a precompiled constant.
…t use `PrimaryExpression()` created two lists on every call: one for chained signs such as `- -1` and one for JSON operators such as `->`. Nearly every primary expression has neither, so both lists usually stayed empty. They are now created at their first element.
`consumeXmlLeadingKeyword` and `FunctionArgumentList` called `Function.getName()` for every function to check for `XMLPARSE`, `XMLELEMENT` or `XMLFOREST`, and `getName()` joins the name parts into a new string on each call. Both checks now compare the single name part.
Contributor
|
Good work, thank you much for tracing and improving this! |
knutwannheden
added a commit
to knutwannheden/JSqlParser
that referenced
this pull request
Oct 31, 2026
Conflicts in JSqlParserCC.jjt, resolved by keeping master's logic and re-adding the node links on top: - PARTITION BY (JSQLParser#2878): the four sites call ComplexExpressionList() directly and still link the PARTITION BY node. - PrimaryExpression (JSQLParser#2874): innerSigns is null without an inner sign, so the loop that wraps and links signed values is guarded. - Condition (JSQLParser#2874): one lazily created deque of NOT tokens carries both the token for the node and whether it was spelled "!".
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Parsing
performance.sql, the JMH benchmark's input, allocates 7.17 MB per parse on master, and about 1 MB of that (15%) does no work. The profile taken for #2870 found the sources: collections created on every call of a production but filled only on a rare path, a regex compiled for everyLIKE, and function names joined into a string just to compare them.This PR is separate from #2870 so each PR's benchmark stays clean.
Condition()created a deque for leadingNOTand!prefixes on every call, though few conditions are negated. An emptyArrayDequeallocates its 17-slot array up front. The deque is now created at the firstNOTor!, as fix: give every parsed object a node spanning its source #2870 already does for the token deque beside it.PrimaryExpression()created two lists on every call, for chained signs such as- -1and for JSON operators such as->. Both are now created at their first element.LikeExpression.KeyWord.fromcalledString.replaceAll("\\s+", "_"), which compiles the pattern on every call. The pattern is now a static constant.consumeXmlLeadingKeywordandFunctionArgumentListcalledFunction.getName()for every function to check forXMLPARSE,XMLELEMENTorXMLFOREST, andgetName()joins the name parts into a new string each time. Both now compare the single name part.Behaviour is unchanged, so there are no new tests.
ExpressionPrecedenceTestpins stacked prefixes (not !1 = 1deparses asNOT ! 1 = 1),DuckDBTestthe chained signs,JsonExpressionRenderingTestthe JSON operators,XmlForestTestthe XML keywords andLikeExpressionTestthe multi-wordSIMILAR TO../gradlew jmh -PjmhProfiler=gc, alternating master (a075737) and this branch on the same machine.gc.alloc.rate.normis bytes per parse:The branch is 2.4% faster in both runs while the 5.4 control moves 2% either way, so the time difference is at the edge of what this benchmark resolves. The allocation difference is not in doubt.
The
Condition()change edits the same lines as #2870, so whichever merges second has a small conflict to resolve. Both deques can then be created together at the firstNOTor!.