-
Notifications
You must be signed in to change notification settings - Fork 852
Description
Description
The type subsumption cache introduced in PR #18875 and partially fixed in PR #18926 causes sustained high CPU usage (~150%) in IDE/FSAC when a project uses generative type providers (e.g. custom HTML type provider with ~15 template instantiations).
PR #18926 memoized TypeStructure creation via a ConditionalWeakTable, but the GetHashCode of TTypeCacheKey still calls GenericHashArbArray on the underlying ImmutableArray<TypeToken> on every cache lookup. Since ImmutableArray<T> is a struct wrapping a T[], F# structural hashing delegates to the underlying array, making each hash call O(n) over the token array. In IDE mode with repeated type checks, this dominates CPU.
A 10-second dotnet-trace CPU sample on the FSAC process (166% CPU) shows:
| Samples | Function |
|---|---|
| 2414 | Thread.PollGC |
| 1170 | RuntimeTypeHandle.InternalAllocNoChecks |
| 820 | GenericEqualityArbArray |
| 696 | TTypeCacheKey.GetHashCode → GenericHashArbArray |
| 96 | ConcurrentDictionary.GrowTable |
| 12 | Cache.rebuildStore |
Repro steps
- Create an F# solution targeting
net10.0(LangVersion 10) with 5+ projects - Add a generative type provider (e.g. one producing ~15 provided types with a few methods each)
- Reference the type provider from 2+ projects in the solution
- Open the solution in VS Code with Ionide (FSAC)
- Observe sustained ~150% CPU on the
dotnet fsautocomplete.dllprocess that does not settle down
A minimal repro with 30 template instantiations in a single project did not reproduce the issue. The combination of multiple projects, generative type provider types, and other type providers (e.g. FSharp.Data JsonProvider) in the same solution appears necessary to trigger it.
Expected behavior
CPU usage should settle to near-zero after initial type checking completes. The subsumption cache should improve performance, not degrade it.
Actual behavior
CPU stays at ~150% indefinitely. The CPU is spent almost entirely in TTypeCacheKey.GetHashCode → GenericHashArbArray, hashing ImmutableArray<TypeToken> arrays on every cache lookup. The cache appears to continuously grow and rebuild, each rebuild re-hashing all existing entries at O(n) per entry.
Diagnostic logging in the type provider confirmed:
- FCS re-creates provider instances every ~10 seconds (observed 12 instances in ~60 seconds)
- Even with static caches returning identical
ProvidedTypeDefinitionobjects (all cache hits), CPU remains high - The provider itself does negligible work — the CPU is entirely in FCS internal processing
Known workarounds
Set <LangVersion>9.0</LangVersion> in Directory.Build.props to disable the UseTypeSubsumptionCache language feature. This eliminates the CPU issue entirely. Requires replacing any F# 10-only syntax (e.g. let! x: T = → let! (x: T) =).
Related information
- macOS (Darwin 25.3.0, Apple Silicon)
- .NET SDK 10.0.103
- Ionide 7.31.1, FSAC 0.83.0, FSharp.Compiler.Service 43.10.101-servicing
- Related issues: #18875 degraded typecheck performance #18925, PR Fix type subsumption cache key perf regression #18926 (partial fix), Performance Regression: Build takes 7s in .net9 and 5min in .net10 #19169 (build regression)
- The suggested fix: cache the hash code inside
TypeStructure(orTTypeCacheKey) soGetHashCodeis O(1) after first computation, not O(n) on every call
Metadata
Metadata
Assignees
Labels
Type
Projects
Status