feat(csharp): add opt-in SpanInputStream variants and OptimizedToken classes - #4941
Open
HarryCordewener wants to merge 1 commit into
Open
HarryCordewener wants to merge 1 commit into
HarryCordewener wants to merge 1 commit into
Conversation
Add new additive-only classes extracted from feature/csharp-runtime-perf-optimizations: - SpanInputStream: base Span-backed char stream (ICharStream adapter) - CharSpanInputStream: char[]-backed stream with Span/Memory API - StringSpanInputStream: string-backed stream with Span/Memory API - OptimizedToken: struct-like token reducing per-token allocations - OptimizedTokenFactory: factory producing OptimizedToken instances Includes xUnit test project (perf-optimizations) covering all new classes. These are purely additive — no modifications to existing runtime files. Signed-off-by: Harry Cordewener <[email protected]>
Contributor
|
@HarryCordewener Sorry I've been busy. I need to invest time fixing the CI before progressing this... |
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.
Summary
This PR adds purely additive, opt-in performance classes to the C# runtime. No existing files are modified — every file here is new. This is a subset extracted from #4938 to make review easier.
Changes
New:
CharSpanInputStreamA replacement for
AntlrInputStreambacked bychar[]rather than copying into the base class's internal structure. Intended for inputs arriving fromTextReader,Stream, or a pre-builtchar[].new CharSpanInputStream(char[], length)— zero-copy construction, ~40 B allocated vs 2 KB forAntlrInputStream.Seek()is O(1) — just_index = value.AntlrInputStream.Seek()loops forward.Dataproperty exposes aReadOnlySpan<char>slice for zero-alloc text access.GetTextSpan(Interval)— zero-allocReadOnlySpan<char>alternative toGetText.net8.0(directICharStream— no virtual dispatch) andnetstandard2.x(extendsBaseInputCharStream).New:
StringSpanInputStreamA replacement for
AntlrInputStreambacked directly by astring. Intended for the common case of parsing an in-memory string.new StringSpanInputStream(string)— stores the reference, zero copy, 40 B allocated._lengthfield (JIT BCE patterns).ToString()returns the original string reference — zero alloc.GetTextusesSubstring()directly, which shares the same BCL path asnew string(ReadOnlySpan<char>)(dotnet/runtime source).New:
OptimizedToken/OptimizedTokenFactoryAn
ITokenimplementation with cachedTextand a[StructLayout(Sequential)]field order to reduce GC header overhead.OptimizedTokenFactory.Defaultis a singleton drop-in forCommonTokenFactory.Default.Micro-optimizations on existing types
ArrayList<T>.Equals— zero-alloc equality via span comparison.IntervalSet— constructor param narrowed fromIList<Interval>toList<Interval>where the implementation already required it.ATNConfighash — no change (ATNConfigSet caches at the set level; per-config caching was benchmarked and found to regress).Benchmarks
A new
tests/benchmarks/project (BenchmarkDotNet) provides head-to-head comparisons. Run with:Benchmark Results
Hardware: Intel Core Ultra 7 265F, .NET 10.0.8, RyuJIT AVX2. All ratios vs
AntlrInputStream.Construction
AntlrInputStreamCharSpanInputStream(char[])StringSpanInputStreamCodePointCharStreamCharSpanInputStreamandStringSpanInputStreamconstruction cost is flat — independent of input length.AntlrInputStreamandCodePointCharStreamscale linearly because they copy the entire input at construction.ConsumeAll (simulated lexer hot loop)
AntlrInputStreamCharSpanInputStreamStringSpanInputStreamCodePointCharStreamStringSpanInputStreamis the standout: 3.6× faster thanAntlrInputStreamat 100 k chars, zero allocation. The zero-alloc result is because the string already lives on the heap — the stream object is the only allocation and the benchmark reuses it.CharSpanInputStreammatchesAntlrInputStreamin the hot loop (samechar[]indexer speed) but saves the construction allocation when built from a pre-built array.Seek
AntlrInputStreamCharSpanInputStreamStringSpanInputStreamCodePointCharStreamStringSpanInputStreamseek is O(1) and completely flat — purely_index = value.GetText
AntlrInputStreamStringSpanInputStreamCodePointCharStreamStringSpanInputStream.GetTextis 3.5× faster and 3.4× less memory at 1 k. At 100 k it is slower in time (but still uses 3× less memory) because the extracted text crosses the LOH threshold (~85,000 bytes), where .NET switches from bump-pointer to free-list allocation and batchesmemmovein 16 KB chunks with GC polls between them. This is a BCL constraint, not a code issue. TheGetTextSpan()method avoids this entirely for callers that can consume a span.LookBack
AntlrInputStreamCharSpanInputStreamStringSpanInputStreamCodePointCharStreamStringSpanInputStreamlookback is flat regardless of input size.Design Decisions
Why
char[]forCharSpanInputStream, notstringorReadOnlyMemory<char>?stringdoesn't supportMemoryMarshal.GetArrayDataReferenceand can't be used asTextReader/Streamsource.ReadOnlyMemory<char>has no indexer — hot-path reads require.Spanproperty access, which constructs a newReadOnlySpan<char>struct on every call.char[]gives a direct JIT intrinsic indexer with full span support. See Span<T> design notes.Why
stringforStringSpanInputStream?For string inputs,
ToCharArray()(used byAntlrInputStreamandCharSpanInputStream) copies every character. Storing the string reference directly is zero-copy. The string indexer is devirtualized by the JIT and compiles to the sameldeleminstruction as array access. The immutable string length enables stronger bounds-check elimination than a mutable_lengthfield.Substring()andnew string(ReadOnlySpan<char>)share the same BCL code path (FastAllocateString + Buffer.Memmove) so there is no advantage to the span ctor forGetText.Why not
MemoryMarshal.GetArrayDataReference+Unsafe.Add?The
(uint)i < (uint)_lengthguard is the standard pattern the JIT is specifically trained to recognise. The branchless>> 31sign-bit trick and the Unsafe pointer path were evaluated and benchmarked — both either matched or regressed vs the simple if/else +_data[pos]. The branch predictor handlesLA(1)(always positive) perfectly, making branches essentially free. Unsafe adds maintenance cost with no measured gain.Why not seal
SingletonPredictionContext?EmptyPredictionContextinherits from it. Sealing would break the existing class hierarchy.Public API compatibility
All existing types (
AntlrInputStream,CommonToken,CommonTokenFactory, etc.) are untouched. New types are purely additive.CharSpanInputStreamandStringSpanInputStreamimplementICharStream;OptimizedTokenimplementsIToken;OptimizedTokenFactoryimplementsITokenFactory. Any call site that accepts an interface can adopt these with no other changes.Testing
tests/perf-optimizations/, covering allICharStreamoperations, parity withAntlrInputStream, edge cases (empty input, EOF, unicode BMP, seek/reset), and integration scenarios.AntlrInputStream's behaviour. Supplementary code points (U+10000+) are two positions, same asAntlrInputStream. UseCodePointCharStream(viaCharStreams.fromString) for grammars that target supplementary characters.