[MicroPerf] Avoid per-call closure allocation in type-hierarchy traversal - #20384
Open
T-Gro wants to merge 2 commits into
Open
[MicroPerf] Avoid per-call closure allocation in type-hierarchy traversal#20384T-Gro wants to merge 2 commits into
T-Gro wants to merge 2 commits into
Conversation
FoldHierarchyOfTypeAux runs on a hot path (InfoReader member/property/field lookup and type inference). Its inner 'let rec loop' was handed to List.foldBack / Option.foldBack / List.exists as a partial application, so a fresh closure was allocated on every traversal. Add a ListInline module (illib.fs) with exists / foldBack whose function argument is inlined at the call site via InlineIfLambda, so the closure never materializes. foldBack folds lists up to length five directly (no allocation) and only copies to an array for longer lists, so it stays stack-safe. Length five is measured: over a real compile GetImmediateInterfacesOfType returns <=5 elements ~96% of the time (81% are empty), so short interface lists allocate nothing at all. At the call sites the top-level 'typeEquiv g ty' stays partially applied (the optimizer fuses it after inlining), but the local 'loop' is passed as a lambda so InlineIfLambda inlines it rather than allocating it as a closure. Fold order is unchanged, so visit order, dedup and the ndeep>100 error are preserved; compiler output is byte-identical under --deterministic+. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: c6610fae-a96e-4421-a920-e73de4fc94dc
Contributor
|
This was referenced Aug 27, 2026
T-Gro
marked this pull request as ready for review
August 27, 2026 13:08
T-Gro
enabled auto-merge (squash)
August 27, 2026 13:08
T-Gro
disabled the stack merge
August 27, 2026 13:11
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
FoldHierarchyOfTypeAuxis on a hot path (member/property/field resolution and type inference). Its innerlet rec loopwas handed toList.foldBack/Option.foldBack/List.existsas a partial application, so a fresh closure was allocated per traversal. Routing the folds through a smallListInlinemodule (exists/foldBackwith[<InlineIfLambda>]) inlines the function argument at the call site, so the closures never materialize; fold order is unchanged.Closures allocated in
FoldHierarchyOfTypeAux(dotnet-tracegc-verbose, 6 self-compiles of a 120-file corpus):loopenvironment (7 captures)loop (ndeep+1)→foldBacktypeEquiv g ty→existsListInline.foldBackfolds lengths ≤ 5 directly (no allocation) and only arrays longer lists (stack-safe, likeList.foldBack). The ≤ 5 cutoff is measured —GetImmediateInterfacesOfTypelength over a real compile (1.0M calls):Output is byte-identical under
--deterministic+(SHA-256 vs base on the same corpus); visit order, thevisitedTycon/visiteddedup,AllowMultiIntfInstantiationshandling and thendeep > 100error are unchanged; interface / inheritance / member-resolution / equality tests pass.