Avoid intermediate slice views when reading token text - #1218
Open
copybara-service[bot] wants to merge 1 commit into
Open
Avoid intermediate slice views when reading token text#1218copybara-service[bot] wants to merge 1 commit into
copybara-service[bot] wants to merge 1 commit into
Conversation
Lexing and parsing turn code point ranges into strings constantly: once for every identifier, keyword and literal. Both call sites spelled this as slice(i, j).toString(), which allocates an intermediate CelCodePointArray view solely to copy out of it and then discard it. Add CelCodePointArray.substring(i, j), which builds the String straight from the backing array, and implement it in each of the four subclasses. toString() becomes final and delegates to substring(0, size()), so the subclasses lose their near-duplicate toString() overrides. Lexer.consumeIdent and PrattParser.getTokenText call the new method. This removes exactly one 32-byte object per token whose text is materialized. Measured with CelParserBenchmark (parseOnly, built -c opt), comparing three parsers back to back in one session: ANTLR, the Pratt parser before this change, and the Pratt parser after it. Objects allocated per parse: | Case | ANTLR | Pratt before | Pratt after | Pratt vs ANTLR | Delta this CL | | :--- | ---: | ---: | ---: | ---: | ---: | | SMOKE_TEST | 357 | 123 | 120 | 3.0x smaller | -2.4% | | CHAINED_ORS | 968 | 349 | 339 | 2.9x smaller | -2.9% | | LIST_COMPREHENSION | 512 | 166 | 160 | 3.2x smaller | -3.6% | | MESSAGE_CREATION | 1,253 | 426 | 406 | 3.1x smaller | -4.7% | | LONG_LIST | 81,794 | 19,265 | 18,263 | 4.5x smaller | -5.2% | Bytes allocated per parse: | Case | ANTLR | Pratt before | Pratt after | Pratt vs ANTLR | Delta this CL | | :--- | ---: | ---: | ---: | ---: | ---: | | SMOKE_TEST | 12,256 | 3,608 | 3,512 | 3.5x smaller | -2.7% | | CHAINED_ORS | 32,160 | 9,912 | 9,592 | 3.4x smaller | -3.2% | | LIST_COMPREHENSION | 17,320 | 4,928 | 4,736 | 3.7x smaller | -3.9% | | MESSAGE_CREATION | 43,128 | 13,056 | 12,416 | 3.5x smaller | -4.9% | | LONG_LIST | 2,907,488 | 563,952 | 531,888 | 5.5x smaller | -5.7% | Wall clock, mean of 3 caliper trial medians: | Case | ANTLR | Pratt before | Pratt after | Pratt vs ANTLR | Delta this CL | | :--- | ---: | ---: | ---: | ---: | ---: | | SMOKE_TEST | 4,940 ns | 696 ns | 684 ns | 7.2x faster | -1.8% | | CHAINED_ORS | 14,641 ns | 2,008 ns | 2,006 ns | 7.3x faster | -0.1% | | LIST_COMPREHENSION | 7,514 ns | 1,250 ns | 1,217 ns | 6.2x faster | -2.7% | | MESSAGE_CREATION | 20,979 ns | 3,526 ns | 3,514 ns | 6.0x faster | -0.4% | | LONG_LIST | 1,616,631 ns | 140,500 ns | 146,640 ns | 11.0x faster | +4.4% | Wall clock is unchanged within measurement noise. The per-case deltas run from -2.7% to +4.4% and straddle zero, which is what a change that removes 3-5% of allocations and no actual work should look like. The LONG_LIST row reads as a regression, but that trial was noisy (per-trial medians 144.6us, 155.7us, 139.6us, against a much tighter 142.1us, 141.1us, 138.3us before) and its fastest observed parse, 134.6us, is below the 135.7us baseline. The win here is allocation volume and the GC pressure that follows from it. This is the first in a series of parser changes; the wall-clock improvements come later in that series. The ANTLR column is included for scale, and shows why the Pratt parser exists. ANTLR is slow enough on LONG_LIST that the case exceeds caliper's default 5 minute per-trial budget and has to be measured with a raised --time-limit. PiperOrigin-RevId: 979610652
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.
Avoid intermediate slice views when reading token text
Lexing and parsing turn code point ranges into strings constantly: once for
every identifier, keyword and literal. Both call sites spelled this as
slice(i, j).toString(), which allocates an intermediate CelCodePointArray view
solely to copy out of it and then discard it.
Add CelCodePointArray.substring(i, j), which builds the String straight from the
backing array, and implement it in each of the four subclasses. toString()
becomes final and delegates to substring(0, size()), so the subclasses lose
their near-duplicate toString() overrides. Lexer.consumeIdent and
PrattParser.getTokenText call the new method.
This removes exactly one 32-byte object per token whose text is materialized.
Measured with CelParserBenchmark (parseOnly, built -c opt), comparing three
parsers back to back in one session: ANTLR, the Pratt parser before this change,
and the Pratt parser after it.
Objects allocated per parse:
Bytes allocated per parse:
Wall clock, mean of 3 caliper trial medians:
Wall clock is unchanged within measurement noise. The per-case deltas run from
-2.7% to +4.4% and straddle zero, which is what a change that removes 3-5% of
allocations and no actual work should look like. The LONG_LIST row reads as a
regression, but that trial was noisy (per-trial medians 144.6us, 155.7us,
139.6us, against a much tighter 142.1us, 141.1us, 138.3us before) and its
fastest observed parse, 134.6us, is below the 135.7us baseline. The win here is
allocation volume and the GC pressure that follows from it. This is the first in
a series of parser changes; the wall-clock improvements come later in that
series.
The ANTLR column is included for scale, and shows why the Pratt parser exists.
ANTLR is slow enough on LONG_LIST that the case exceeds caliper's default 5
minute per-trial budget and has to be measured with a raised --time-limit.