Skip to content

Improve generation scaling and memory use (#139) - #143

Merged
AdaskoTheBeAsT merged 5 commits into
masterfrom
perf/139-generation-optimizations
Nov 8, 2026
Merged

AdaskoTheBeAsT merged 5 commits into
masterfrom
perf/139-generation-optimizations

Conversation

@AdaskoTheBeAsT

@AdaskoTheBeAsT AdaskoTheBeAsT commented Nov 7, 2026 •

Copy link
Copy Markdown
Owner

Summary

Closes #139.

Improve generation scaling for large project graphs and templates that inspect thousands of declarations while producing relatively little output. This includes the earlier indexing/caching work, the follow-up CodeModel laziness, memory-lifetime fixes, bounded performance diagnostics, regression tests, and the Buildalyzer logger correction.

The changes apply the same lazy-adaptation approach as the reporter's performance branch, with additional project/template indexing and cache-lifetime work. Local comparisons used that branch at 614de06, not the reporter's private solution.

What changed

Lazy CodeModel adaptation and lifetime

  • Lazily initialize Type/Class default collections and Class.Type, preserving virtual init properties and explicit initializers, including null.
  • Adapt engine-owned collections per item. Count does not create adapters, short-circuit traversal only creates visited items, and empty collections skip deferred machinery.
  • Defer attribute values, attribute types, and argument Type/TypeValue. Capture mapping settings at the existing construction boundary; retain eager behavior for custom Settings and public enumerable constructors.
  • Preserve real source-file/member parent navigation, partial declarations, nested types, generic substitution, and stable adapter identities.
  • Replace the strong adapter-to-metadata lookup with weak keys so filtering does not retain every discarded adapter.
  • Use extractor-owned, reference-identity type-reference entries instead of dependent-handle ownership that prolonged old extraction graphs through lazy reference edges.
  • Share type mapping and memoize completed generic substitutions rather than repeating work for shared reference shapes.

Project and metadata scaling

  • Index source-file/declaration lookup, dependency closures, and dirty paths instead of repeatedly scanning the full graph.
  • Keep project metadata graphs local until merged, avoiding repeated eager transitive metadata copying.
  • Reuse project-owned stamped syntax trees on source-only rebuilds, including beyond the global tree cache's 8,192-entry capacity. Preserve parse configuration and conservative handling of source changes during parsing.
  • Reuse bounded stamped assembly-reference images in Engine and Roslyn, preserving reference properties and invalidation without keeping DLL handles open.
  • Reuse solution setup within one metadata request through a serialized Buildalyzer session. Sessions retain at most one workspace and do not cross requests. Restore, SDK/framework selection, node reuse, and individual project evaluations are unchanged.

Template and rendering scaling

  • Share source slices, balanced-block lookup, identifiers, inline-predicate lookup, and line maps across repeated rendering, replacing the old substring-oriented block reader.
  • Cache compiled helper reflection bindings with weak host-type keys, preserving collectible contexts and per-generation helper/static-state boundaries.
  • Reuse source/declaration indexes during render planning and helper adaptation.
  • Use nonbacktracking matching for supported glob/template patterns and return immediately for unchanged unified diffs.
  • Keep embedded-language scanning aligned with the shared span-based scanner.

Diagnostics, build, and documentation

  • Extend TYPEWRITER_TRACE_PERF with request-local metadata stages, bounded per-template render summaries, planning/writing/formatting times, and clearly labelled process-wide allocation/GC observations.
  • Keep tracing disabled by default; performance observers are not retained in cached metadata.
  • Pin Buildalyzer to ad40feb, published on fix/typewriter-logger-restore-imports. Separate the full-framework logger's restore/build evaluations so restored package targets load on the first build, resolving the System.ValueTuple conflict without suppressing warnings or changing package versions.
  • Update perf-opt.md with measurements, comparison limits, reproduction details, and remaining work.

Comparison with the reporter's branch

The attribute-heavy probe uses 13,563 classes, four attributes per class, nested type references, five helper-filter templates, and 977 declarations sharing one source path. The metadata probe uses the real 9,275-file fixture through a supplied loader, excluding MSBuild. Focused results are medians from three rotating fresh-process runs per variant; MB is decimal.

Matched local scenario Reporter branch This PR
Attribute-heavy first request 1,784 ms / 815.0 MB allocated 1,880 ms / 768.6 MB
Attribute-heavy repeat 1 778 ms / 756.3 MB allocated 723 ms / 718.2 MB
Attribute-heavy repeat 2 1,435 ms / 756.2 MB allocated 1,225 ms / 718.4 MB
Attribute-process final peak 427.7 MB 306.9 MB
Metadata first one-file edit 5,925 ms / 1,110.2 MB allocated 3,993 ms / 677.9 MB
Metadata second one-file edit 5,358 ms / 1,112.2 MB allocated 3,072 ms / 687.6 MB
Parsed sources per edit 9,275 1
Live managed memory after two edits, forced-GC probe 435.7 MB 393.4 MB
Ordinary metadata-process final peak 1,030.4 MB 1,079.7 MB

This is not an across-the-board speed or peak-memory win:

  • Attribute allocations are about 5% lower and attribute-process peak memory about 28% lower, but the first request is about 5% slower. Repeat medians improve, while worst repeat samples are slower than the reporter's.
  • The retention fix reduces live memory after two edits from about 818 MB in the pre-laziness local snapshot to 393 MB. That snapshot already includes earlier changes in this PR; it is not an origin/master baseline.
  • Lower live memory does not imply lower natural peak memory. The ordinary metadata-process peak remains about 1.08 GB.
  • Solution setup reuse saves milliseconds, not the dominant restore/evaluation cost. No material cold-MSBuild speedup is established.

For the same supported Models.tst CLI workload, all variants produce the independently expected 464 files with matching original hashes:

Fresh-process sample Reporter branch This PR
Dry run 39.92 s 35.54 s
Actual write 35.09 s 35.06 s

These are one sample per mode, not interchangeable repetitions. The already optimized pre-laziness snapshot took 29.03 s in the write sample, so the CLI results do not establish a reliable overall speedup.

The reporter branch produces only 467 of 931 expected outputs on the full five-template fixture because all 464 inline-predicate-only Others.tst outputs are missing. That older branch's full-fixture timing is therefore not equivalent work. The shared-template comparison supplements, rather than replaces, this PR's unchanged 931-output compatibility gate.

The reporter's private, mostly net48 solution was not available. These synthetic net10.0 measurements must not be presented as reproducing its reported gains. Buffer/cache-capacity limits remain unchanged; restore, diagnostics, source generators, and project references were not disabled.

Validation

  • Isolated full solution Release build: zero warnings and zero errors.
  • Full solution test suite: 693 passed, 0 failed, 0 skipped.
  • Candidate full five-template actual-write run: all 931 filenames and SHA-256 hashes match the original snapshots, with zero diagnostics.
  • Shared CLI actual-write workload: all three variants' 464 filenames and hashes match the original snapshots.
  • Validated 135 focused observations, 72 metadata digests, and 27 attribute-output digests.
  • Regression coverage includes cache capacity/configuration/eviction, successive edits, source stamps, reference properties and file handles, cancellation/retry, session boundaries, changed project shape, custom settings, parent navigation, partial declarations, concurrent lazy access, collectible bindings, parser diagnostics, and old-adapter/compilation reclamation.
  • Main repository and CRLF-aware submodule whitespace checks pass.

Local validation used serial MSBuild (-m:1 -nr:false) and isolated --artifacts-path outputs so the running language server was not stopped or its locked outputs overwritten. Raw probes/logs remain in ignored local artifacts; the recorded results and remaining-work plan are in perf-opt.md.

Copilot AI balanced review requested due to automatic review settings November 7, 2026 09:21

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

Copilot AI balanced review requested due to automatic review settings November 8, 2026 14:59

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

Copilot AI balanced review requested due to automatic review settings November 8, 2026 16:08

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

Copilot AI balanced review requested due to automatic review settings November 8, 2026 16:18

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

Copilot AI balanced review requested due to automatic review settings November 8, 2026 16:20

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

@AdaskoTheBeAsT
AdaskoTheBeAsT merged commit 4a882f2 into master Nov 8, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CLI runs slowly on large .NET Framework project

2 participants