Skip to content

Comments

Cache overload resolution results for repeated method calls (preview language feature)#19072

Merged
T-Gro merged 105 commits intomainfrom
copilot/create-performance-profiling-automation
Feb 19, 2026
Merged

Cache overload resolution results for repeated method calls (preview language feature)#19072
T-Gro merged 105 commits intomainfrom
copilot/create-performance-profiling-automation

Conversation

Copy link
Contributor

Copilot AI commented Nov 11, 2025

Performance

Measured on 5000 Assert.Equal(x, y) calls where x and y are let-bound integers:

Typecheck Phase Duration

Compiler Typecheck (s) vs .NET 10 vs .NET 9
.NET 9 SDK 40.8 +528% baseline
.NET 10 SDK 6.5 baseline -84%
This PR 2.9 -55% -93%

Speedup: 2.2x faster than .NET 10 SDK, 14x faster than .NET 9 SDK

GC Pressure Reduction

Compiler GC0 collections GC1 GC2
.NET 10 SDK 115 28 3
This PR 21 3 1

GC0 collections reduced by 82%

Cache Statistics

Cache Hit Ratio Hits Misses
overloadResolutionCache 99.98% 4999 1
typeSubsumptionCache 81% 601 141

Why

Overload resolution is expensive. When the same overloaded method is called repeatedly with the same argument types (common in test files with Assert.Equal), the compiler redundantly recomputes the same resolution.

Strategy

Cache the resolution result using a composite key:

  • Method group hash - identity of the available overloads
  • Caller argument types - structural fingerprint of each argument type
  • Object argument types - critical for extension methods
  • Expected return type - for disambiguation (e.g., methods with out args)
  • Type argument count - for generic calls

When the same key is seen again, return the cached winner index.

Handling Type Stability

TypeHashing generates a TypeStructure for each type, which can be:

Structure Meaning Caching Behavior
Stable Fully resolved, won't change ✅ Cache
Unstable (solved typars) Contains type variables that have solutions ✅ Cache (see below)
Unstable (unsolved typars) Contains unsolved flexible typars ❌ Skip
PossiblyInfinite Type exceeds 256 tokens ❌ Skip

Why Solved Typars Are Safe to Cache

When TypeHashing encounters a solved typar (e.g., 'a solved to int), it:

  1. Emits the solution type tokens (int), not the typar itself
  2. Marks the structure as Unstable (because Trace.Undo could theoretically revert it)

However, in overload resolution context, this is safe because:

  • Cache keys are computed before FilterEachThenUndo runs
  • Caller argument types are already resolved at this point
  • Any solved typars in those types won't be reverted by the speculative resolution undo

We reject Unstable structures only when they contain TypeToken.Unsolved - these are flexible typars that could resolve to different types in different contexts.

Before/After Key Strategy

To maximize cache hits:

  1. Compute cache key before resolution (may have solved typars marked Unstable)
  2. Perform resolution (may solve more types)
  3. Compute cache key after resolution (types now fully solved)
  4. Store result under both keys if they differ

This allows future calls with already-solved types to hit the cache directly.

Safety Constraints

Caching is disabled for:

  • Named arguments (complex matching logic)
  • SRTP/trait constraints (resolution is context-dependent)
  • op_Explicit/op_Implicit conversions
  • Single-candidate scenarios (no benefit)

Language Feature Gating

The caching optimization is gated behind a new MethodOverloadsCache language feature:

  • Enabled: Only with --langversion:preview
  • Disabled by default: Safe fallback to standard resolution

When the language feature is not supported:

  • Cache key computation is skipped entirely
  • No cache lookup or storage occurs
  • Falls back to standard overload resolution

This provides a safe rollout path for testing the optimization before enabling it by default in a future F# release.

Code Organization

Cache logic is isolated in a dedicated module OverloadResolutionCache.fs:

  • OverloadResolutionCacheKey - cache key type with method hash and type structures
  • tryComputeOverloadCacheKey - key computation with stability checks
  • storeCacheResult - stores under before/after keys for maximum cache hits (respects language feature)
  • hasUnsolvedTokens - detects flexible typars that must not be cached

TypeHashing.fs is unchanged from main branch (only MaxTokenCount = 256 constant extracted).

Testing

67 E2E tests verify correct overload selection (not just compilation):

  • 44 basic tests: primitives, multi-args, ParamArray, type hierarchy, extension methods, optional args, rigid typars, tuples, named arguments, constraints, type-directed conversions, out params
  • 23 adversarial tests: cache poisoning attempts with alternating types, generic instantiations, subtype overloads, nested generics, byref overloads, stress test with 100 alternating calls

Each test returns a string identifying which overload was picked, verified at runtime.

All tests use withLangVersionPreview to enable the MethodOverloadsCache feature.


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI and others added 3 commits November 11, 2025 13:31
Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com>
Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com>
Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com>
Copilot AI changed the title [WIP] Create comprehensive performance profiling automation for xUnit Add automated performance profiling suite for xUnit Assert.Equal compilation issue (#18807) Nov 11, 2025
Copilot AI requested a review from T-Gro November 11, 2025 13:49
…t.Equal test run

Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com>
… path details

Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com>
Copilot AI changed the title Add automated performance profiling suite for xUnit Assert.Equal compilation issue (#18807) Add automated performance profiling suite with trace analysis for xUnit Assert.Equal compilation issue (#18807) Nov 11, 2025
…avior

Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com>
… rejected

Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com>
Copilot AI requested a review from T-Gro November 14, 2025 13:36
@github-actions
Copy link
Contributor

github-actions bot commented Jan 20, 2026

❗ Release notes required


✅ Found changes and release notes in following paths:

Warning

No PR link found in some release notes, please consider adding it.

Change path Release notes path Description
src/Compiler docs/release-notes/.FSharp.Compiler.Service/10.0.300.md No current pull request URL (#19072) found, please consider adding it
LanguageFeatures.fsi docs/release-notes/.Language/preview.md

…mprovement

Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 11, 2026

@copilot : Add release notes to the 10.0.300 .md file pointing to this PR and giving a one-line about what it is.

Added release notes entry to docs/release-notes/.FSharp.Compiler.Service/10.0.300.md (commit c909230):

Overload resolution results are now cached, providing significant performance improvements for code with repeated method calls. (Issue #18807)

Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com>
Copilot AI changed the title Cache overload resolution results for repeated method calls Cache overload resolution results for repeated method calls (preview language feature) Feb 11, 2026
…utionCache.fs

The MethodOverloadsCache language feature check added in commit 6333f49
uses LanguageFeature.MethodOverloadsCache but the module was missing
the required open FSharp.Compiler.Features import, causing FS0039
across all CI jobs.
…lease note

- PERF: Change cache key fields from list to array (better cache locality, no Seq.toList)
- PERF: Pre-compute retTyOpt and anyHasOutArgs once in ResolveOverloading, pass through to avoid redundant List.exists calls
- HONEST-ASSESSMENT: Fix test name '95 percent' to match actual 70% assertion threshold
- NO-LEFTOVERS: Restore accidentally removed TFM centralization release note entry
PERF: Replace heap-allocated tuples in computeMethInfoHash with combineHash.
CODE_QUALITY: Remove pass-through wrappers, deduplicate 5 identical
  ResolveOverloadingCore calls into single fallthrough path.
TEST_COVERAGE: Use parseAndCheckScriptPreview so cache is actually exercised.
TEST_CODE_QUALITY: Parameterize 9 identical tests into Theory, remove printfn.
NO_LEFTOVERS: Remove redundant inline comments.
…, clean up redundant comments

- Removed retTyOpt, anyHasOutArgs, cacheKeyOpt, cache params from GetMostApplicableOverload
- Moved storeCacheResult call to caller site in ResolveOverloadingCore
- Removed 4 redundant inline comments from test files
- Remove unused _trace parameter from ResolveOverloadingCore
- Remove CachedFailed variant (stored but never consumed on lookup)
- Clean up redundant comments across compiler and test files
- Condense verbose XML doc comments on internal members
- Add missing featureMethodOverloadsCache entry to all 13 XLF translation files
  (FSComp.txt was modified in 6333f49 but XLF was never updated)
- Fix fantomas formatting in OverloadResolutionCache.fs/.fsi and Caches.fs
- Add Language preview release note for MethodOverloadsCache feature
@github-project-automation github-project-automation bot moved this from New to In Progress in F# Compiler and Tooling Feb 18, 2026
@T-Gro T-Gro enabled auto-merge (squash) February 18, 2026 19:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

5 participants