Skip to content

Cache package-derived maps during .NET program generation - #1120

Closed
corymhall wants to merge 2 commits into
mainfrom
perf/dotnet-package-context-cache
Closed

Cache package-derived maps during .NET program generation#1120
corymhall wants to merge 2 commits into
mainfrom
perf/dotnet-package-context-cache

Conversation

@corymhall

@corymhall corymhall commented Aug 11, 2026

Copy link
Copy Markdown
Member

Provider documentation generation binds each example separately but reuses one pcl.PackageCache. This means thousands of programs can use the same package objects. GenerateProgram currently scans those packages and rebuilds the same C# lookup maps for every example.

This change caches the namespace, compatibility, module, function argument, and token ownership maps. Cache entries use package identity and resource, function, and type counts, so the maps are rebuilt when a partial package grows. The cache is bounded, and each generator receives its own mutable top-level namespace map.

Measured across the complete AWS PCL corpus with the first PR applied:

Metric Before After Change
.NET generation 8,584 ms 1,726 ms -79.9%
User CPU 10,005 ms 1,991 ms -80.1%
System CPU 141 ms 50 ms -64.7%
Allocated bytes 6.30 GB 1.15 GB -81.7%
Allocations 19,316,973 18,934,626 -2.0%
GC cycles 52 8 -84.6%
GC pause 3.726 ms 0.461 ms -87.6%
Peak RSS 2.14 GB 764 MB -64.3%
Parse time 2,752 ms 2,708 ms -1.6%
Bind time 8,804 ms 8,793 ms -0.1%

Together, the two PRs change the primary and resource metrics from the original baseline as follows:

Metric Original baseline Both PRs Change
.NET generation 15,555 ms 1,726 ms -88.9%
User CPU 20,157 ms 1,991 ms -90.1%
System CPU 312 ms 50 ms -84.1%
Allocated bytes 19.97 GB 1.15 GB -94.2%
Allocations 178,920,998 18,934,626 -89.4%
GC cycles 120 8 -93.3%
GC pause 10.064 ms 0.461 ms -95.4%
Peak RSS 1.00 GB 764 MB -23.9%

The generated files, diagnostics, and workload counts remained unchanged.

Validation:

  • mise exec -- make format_language_host_check
  • mise exec -- make lint_language_host
  • mise exec -- make test_codegen

@corymhall

Copy link
Copy Markdown
Member Author

corymhall added a commit that referenced this pull request Aug 11, 2026
Summary:
- Record the repeated .NET program generation performance improvement.

Rationale:
- Include the required runtime changelog fragment for PR #1120.

Tests:
- not run (metadata-only change)
@corymhall
corymhall force-pushed the perf/dotnet-package-context-cache branch from 94fe3f5 to 4b9623a Compare August 11, 2026 19:17
@corymhall corymhall changed the title perf(codegen): cache package-derived lookup maps Cache package-derived maps during .NET program generation Aug 11, 2026
@corymhall
corymhall requested a review from pulumi-eon August 11, 2026 20:20
@eon-pulumi

eon-pulumi Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Verdict: Approved

Mechanical hoist of existing per-call schema-scanning logic into a bounded, identity+shape-keyed cache; traced every read/write site of the cached maps and confirmed the shared references are never mutated after caching (only the per-generator namespaces clone is written). CI is fully green across the build/format/lint/codegen/conformance/integration matrix, and a targeted unit test covers the cache-invalidation-on-growth behavior. Automated low-risk assessment, not a substitute for human review.


View session · Was this review helpful? Yes · No

@eon-pulumi-agent eon-pulumi-agent Bot 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.

Reviewed the diff for PR #1120 (commits 5037e1c and 4b9623a): pulumi-language-dotnet/codegen/gen_program.go, its test, and the changelog fragment.

What I checked

  • Correctness of the new packageContextFor cache: cache-key matching (pointer identity + resource/function/type counts) correctly forces a rebuild when a partial package grows, matching the documented assumption that pcl.PackageCache packages only grow between calls (verified via a dedicated unit test, TestPackageContextCacheTracksPackageShape).
  • Whether the cached maps handed out to each generator are ever mutated after being cached. Only namespaces is deep-enough-cloned per generator (maps.Clone); the other four maps (compatibilities, tokenToModules, functionArgs, tokenPackages) are shared references. I traced every read/write site of all five fields in gen_program.go and confirmed the other four are read-only everywhere, and the one write site for namespaces (g.namespaces[pkg] = info.Namespaces) only replaces a key in the per-call clone, never mutating a shared inner map — so the sharing is safe.
  • Security: no injection/authn/secrets/SSRF surface — this is pure in-memory Go map/slice manipulation over trusted schema structs with a bounded (16-entry) cache, so no DoS or cross-tenant leakage concern.
  • AGENTS.md compliance: changelog fragment present and well-formed (component: runtime, kind: Improvements), no submodule/generated-file/golden-file violations, formatting looks consistent with gofumpt conventions.
  • CI: all required checks (build, format, lint, codegen-tests, conformance-tests, integration-tests across the ubuntu/macos/windows x .NET-version matrix) are green.

One pre-existing observation (not introduced by this PR, not blocking)

Left an inline note on a data race that already existed in the pre-PR code: ImportLanguages mutates the shared *schema.Package without any synchronization, and this PR's rebuild path still calls it outside the cache mutex. Since the PR only reduces how often this path runs (cache hits skip it entirely), it's a pre-existing hazard rather than a regression, so it isn't blocking here — but worth a follow-up if GenerateProgram can be invoked concurrently for the same shared package objects in practice.

Overall this is a well-scoped, mechanical hoist of existing per-call logic into a shared cache, with a solid before/after benchmark and a targeted regression test for the new invalidation behavior. This is an automated low-risk assessment, not a substitute for human review.

Comment thread pulumi-language-dotnet/codegen/gen_program.go
@eon-pulumi
eon-pulumi Bot removed the request for review from pulumi-eon August 11, 2026 20:32
@corymhall
corymhall force-pushed the perf/dotnet-canonical-token branch from 0607c3b to 8c9944f Compare August 12, 2026 12:11
corymhall added a commit that referenced this pull request Aug 12, 2026
Summary:
- Record the repeated .NET program generation performance improvement.

Rationale:
- Include the required runtime changelog fragment for PR #1120.

Tests:
- not run (metadata-only change)
@corymhall
corymhall force-pushed the perf/dotnet-package-context-cache branch from 4b9623a to 663540f Compare August 12, 2026 12:11
@corymhall
corymhall marked this pull request as ready for review August 12, 2026 12:29
@corymhall
corymhall requested a review from a team as a code owner August 12, 2026 12:29

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 663540f307

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

tokenPackages: make(map[string]string),
}
entry := &packageContextCacheEntry{
packages: slices.Clone(packages),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid retaining per-request package graphs globally

When the language host handles distinct GenerateProgram RPCs, main.go creates a fresh cached loader for each request, so these package pointers cannot produce cache hits across requests. Cloning them into this process-global cache nevertheless keeps the complete schema graphs from the last 16 requests reachable after their loaders close; for large providers or a long-lived host processing multiple projects, this can permanently add substantial memory and potentially cause OOMs. Scope the cache to the shared package cache/bulk-generation operation rather than retaining unrelated request packages globally.

Useful? React with 👍 / 👎.

Base automatically changed from perf/dotnet-canonical-token to main August 19, 2026 18:29

@unblocked unblocked Bot 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.

✅ No issues found

About Unblocked

Unblocked has been set up to automatically review your team's pull requests to identify genuine bugs and issues.

📖 Documentation — Learn more in our docs.

💬 Ask questions — Mention @unblocked to request a review or summary, or ask follow-up questions.

👍 Give feedback — React to comments with 👍 or 👎 to help us improve.

⚙️ Customize — Adjust settings in your preferences.

Summary:
- Reuse immutable C# package lookup maps across program generation calls.
- Invalidate cached contexts when a partial package gains schema members.
- Bound retained contexts and preserve per-generator namespace mutation.

Rationale:
- Bulk documentation generation binds separate programs against shared package
  objects and previously rebuilt the same maps for every example.
- This reduced AWS corpus generation time from 8.58s to 1.73s on top of the
  canonical-token optimization.

Tests:
- mise exec -- make format_language_host_check
- mise exec -- make lint_language_host
- mise exec -- make test_codegen
Summary:
- Record the repeated .NET program generation performance improvement.

Rationale:
- Include the required runtime changelog fragment for PR #1120.

Tests:
- not run (metadata-only change)
@corymhall
corymhall force-pushed the perf/dotnet-package-context-cache branch from 663540f to d26fae5 Compare August 20, 2026 11:34
@corymhall corymhall closed this Aug 20, 2026
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.

1 participant