Cache hash code in TypeStructure to fix O(n) GetHashCode perf regression#19369
Cache hash code in TypeStructure to fix O(n) GetHashCode perf regression#19369OnurGumus wants to merge 1 commit intodotnet:mainfrom
Conversation
TypeStructure.GetHashCode was calling GenericHashArbArray on the underlying TypeToken[] on every cache lookup, making each hash O(n). In IDE mode with generative type providers this caused sustained ~150% CPU as the type subsumption cache continuously rehashed all entries. Pre-compute the hash when creating TypeStructure and store it in the DU case. Add [<CustomEquality; NoComparison>] with a fast-path Equals that checks the cached hash before comparing arrays. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
❗ Release notes requiredCaution No release notes found for the changed paths (see table below). Please make sure to add an entry with an informative description of the change as well as link to this pull request, issue and language suggestion if applicable. Release notes for this repository are based on Keep A Changelog format. The following format is recommended for this repository:
If you believe that release notes are not necessary for this PR, please add NO_RELEASE_NOTES label to the pull request. You can open this PR in browser to add release notes: open in github.dev
|
|
@OnurGumus please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.
Contributor License AgreementContribution License AgreementThis Contribution License Agreement ( “Agreement” ) is agreed to by the party signing below ( “You” ), 1. Definitions. “Code” means the computer software code, whether in human-readable or machine-executable form, “Project” means any of the projects owned or managed by .NET Foundation and offered under a license “Submit” is the act of uploading, submitting, transmitting, or distributing code or other content to any “Submission” means the Code and any other copyrightable material Submitted by You, including any 2. Your Submission. You must agree to the terms of this Agreement before making a Submission to any 3. Originality of Work. You represent that each of Your Submissions is entirely Your 4. Your Employer. References to “employer” in this Agreement include Your employer or anyone else 5. Licenses. a. Copyright License. You grant .NET Foundation, and those who receive the Submission directly b. Patent License. You grant .NET Foundation, and those who receive the Submission directly or c. Other Rights Reserved. Each party reserves all rights not expressly granted in this Agreement. 6. Representations and Warranties. You represent that You are legally entitled to grant the above 7. Notice to .NET Foundation. You agree to notify .NET Foundation in writing of any facts or 8. Information about Submissions. You agree that contributions to Projects and information about 9. Governing Law/Jurisdiction. This Agreement is governed by the laws of the State of Washington, and 10. Entire Agreement/Assignment. This Agreement is the entire agreement between the parties, and .NET Foundation dedicates this Contribution License Agreement to the public domain according to the Creative Commons CC0 1. |
Document the FSharp_CacheEvictionImmediate=1 env var workaround for the FCS type subsumption cache hashing bug (dotnet/fsharp#19369). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Lots of calls to |
Summary
TypeStructure.GetHashCodecallsGenericHashArbArrayon the underlyingTypeToken[]on every cache lookup, making each hash computation O(n) over the token array. In IDE mode with generative type providers, the type subsumption cache continuously rehashes all entries duringrebuildStore, causing sustained ~150% CPU.This PR pre-computes the hash when creating
TypeStructureand stores it in the DU case, makingGetHashCodeO(1). It also adds[<CustomEquality; NoComparison>]with a fast-pathEqualsthat checks the cached hash before comparing arrays.Background
A 10-second
dotnet-traceCPU sample on the FSAC process (166% CPU) with a solution using generative type providers shows:Thread.PollGCRuntimeTypeHandle.InternalAllocNoChecksGenericEqualityArbArrayTTypeCacheKey.GetHashCode→GenericHashArbArrayConcurrentDictionary.GrowTableCache.rebuildStorePR #18926 memoized
TypeStructurecreation via aConditionalWeakTable, butGetHashCodestill delegates to F# structural hashing onImmutableArray<TypeToken>(which wrapsT[]), making each call O(n). The cache'srebuildStorere-hashes all existing entries, amplifying the cost.Repro steps
net10.0(LangVersion10) with 5+ projects.dotnet fsautocomplete.dllprocess that does not settle down.Setting
<LangVersion>9.0</LangVersion>(disablingUseTypeSubsumptionCache) eliminates the CPU issue entirely.Changes
src/Compiler/Utilities/TypeHashing.fs: AddedhashTokenArrayhelper. ChangedTypeStructureDU cases to carry a pre-computedhash: int. Added[<CustomEquality; NoComparison>]with O(1)GetHashCodeand fast-pathEquals.src/Compiler/Checking/OverloadResolutionCache.fs: Updated pattern matches for newStable(hash, tokens)/Unstable(hash, tokens)shape.Related