Optimize: Reduce allocations in source generator hot paths - #28
Merged
Conversation
…ot paths - Single-pass partitioning for site classification and scalar/collection param split - BuilderKind enum replaces scattered string.Contains() checks across 5 files - Pre-grouped staticFields lookup eliminates per-interceptor linear scans - StringBuilder for SQL placeholder replacements in loops - Mutable ExpressionTranslationContext.WithJoinedEntity() eliminates dictionary copies - Array allocations instead of List for join entity type collections - Single-pass string escaping in SyntacticClauseTranslator and shared helper in ExpressionSyntaxTranslator Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…roaches - Finding #4: Combine GetConstantValue + GetTypeInfo into single flow in TranslateCapturedValue, eliminating redundant semantic model query on non-constant captured expressions - Finding #8: Add SymbolDisplayCache using ConditionalWeakTable to cache ToDisplayString results across invocations for the same ITypeSymbol, avoiding ~3,000 redundant string allocations for ~20 unique entity types - Finding #10: Deferred update pattern for dictionary iteration in TokenizeCollectionParameters, avoiding .ToList() key copy by collecting pending updates and applying after iteration Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DJGosnell
force-pushed
the
fix/25-reduce-generator-allocations
branch
from
March 20, 2026 03:50
4ceefad to
7f2c6ed
Compare
DJGosnell
added a commit
that referenced
this pull request
Apr 29, 2026
(A) findings — addressed in code: - Doc comments on `LikeExpr.NeedsEscape` and `EscapeLikeMetaChars` updated to flag dialect-aware render-time escaping (#1) - New `MaybeDoubleBackslashes` helper: prefix/suffix in non-folded LIKE concat path now defensively backslash-doubled for MySQL+default (#7) - Inline comments at both literal-pattern emission sites explain the single-quote-first / backslash-double-second order (#9) - `SqlDialectConfig.ParseAttribute` returns `(Config, Schema)` tuple in a single pass; `ContextParser` consumes the tuple (#28) - Code comment in `ParseAttribute` documents the SQLite default for missing `Dialect=` as intentional pre-existing behavior (#11) - All 11 method bodies in `SqlAssembler.cs` now uniformly use the `var dialect = config.Dialect;` shadow at entry (#25) - Extracted `IsDockerUnavailable`, `TableExistsAsync`, `ExecAsync` from the two MySQL test containers into shared `TestContainerHelpers.cs` (#26) - Added `<remarks>` footgun warning to the back-compat `Render(SqlDialect)` overload — silently defaults the carrier flag (#33) - Updated `MySqlBackslashEscapes` XML doc to make non-MySQL no-op behavior explicit; future QRY rule noted as follow-up (#37) (B) findings — added gap-filling tests: - `Contains_ParameterBound_BackslashEscapesTrue_NoDoubling` proves the parameter-bound LIKE path bypasses doubling (#20) - `Where_Contains_LiteralBackslash_DefaultMode_ReturnsMatch` integration test exercises the seeded-but-unqueried `"a\b"` row through the full pipeline against live default-mode MySQL (#21) - `Where_Contains_AnsiForm_NoBackslashEscapesSession_ReturnsMatch` integration test exercises the opt-out roundtrip with new `MyAnsiSessionDb` context against default-mode container with session-level `SET sql_mode = ...,NO_BACKSLASH_ESCAPES` (#2) All 3,384 tests pass.
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
Reduces allocations and redundant work across 14 files in the source generator's hot paths. With ~3,000 interceptors in the test project, these optimizations target per-site and per-interceptor overhead: single-pass collection partitioning, a
BuilderKindenum replacing scatteredstring.Contains()checks, pre-grouped field lookups,StringBuilderfor in-loop string mutations, mutable translation context,ConditionalWeakTable-backedToDisplayStringcaching, and deferred dictionary updates.Reason for Change
The generator produces correct output but has accumulated allocation-heavy patterns that compound across ~3,000 interceptor sites — triple-filtering collections, duplicating partitions, allocating intermediate strings in loops, copying dictionaries on every join, and performing repeated
string.Contains()type classification across 5 files.Impact
.Where().ToList()calls for site classificationStringBuilder.Replacefor SQL placeholder and raw SQL parameter substitution loopsGetConstantValue+GetTypeInfointo single flow, eliminating redundant semantic model query per non-constant captured expressionDictionary<string, List<CachedExtractorField>>eliminates per-interceptor O(N) linear scansBuilderKindenum replaces ~15 scatteredstring.Contains()checks across 5 files with single classification at site creationWithJoinedEntity()mutates dictionary in place — callers reassign immediately so immutability was unnecessarySymbolDisplayCacheusingConditionalWeakTable<ITypeSymbol, string>caches display strings across ~3,000 invocations for ~20 unique entity typesToArray()instead ofToList()for join entity type collections that are only iteratedStringBuilderescaping; sharedEscapeForCSharpStringhelperMigration Steps
None — internal generator changes only.
Performance Considerations
All changes reduce allocations and CPU work. The
ConditionalWeakTablecache replaces far more string allocations than it introduces. TheBuilderKindenum adds one byte perUsageSiteInfobut eliminates multiple string scans downstream.Security Considerations
None.
Breaking Changes
BuilderKindenum and property added toUsageSiteInfostaticFieldsparameter changed fromListto pre-filteredList?inGenerateWhereInterceptor,GenerateJoinedWhereInterceptor,GenerateModificationWhereInterceptorExpressionTranslationContext.WithJoinedEntity()now mutates in place instead of returning a copyTryInlineConstantremoved (inlined intoTranslateCapturedValuewithTryInlineConstantFromCompilationfallback)SymbolDisplayCacheutility added; Analyzers project now linksUtilities/folder🤖 Generated with Claude Code