[MicroPerf] Inline the remaining free-variable foldBacks - #20439
Open
T-Gro wants to merge 4 commits into
Open
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
accFreeInTyparConstraints and accFreeInTypars fold accFreeInTyparConstraint / accFreeTyparRef over a typar's constraint and typar lists during free-variable computation, one of the hottest traversals in the compiler. Passing those top-level functions to List.foldBack allocated a partial- application closure per call, over typically tiny (often empty) lists where the closure is the entire cost. Switch both folds to ListInline.foldBack (added in the parent change), whose InlineIfLambda folder inlines the top-level partial application, so no closure is allocated. Fold order is unchanged; compiler output is byte-identical under --deterministic+. Measured (dotnet-trace gc-verbose, 6 self-compiles of a 120-file corpus): accFreeInTyparConstraints closure 147.8 MB -> 0, accFreeTyparRef closure 238.8 MB -> 0. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: c6610fae-a96e-4421-a920-e73de4fc94dc
The remaining List.foldBack / List.fold sites in the free-variable walkers (accFreeInFlatExprs, boundTypars and its left-to-right twin, accFreeInMeasure, the LetRec/Obj/target walkers, and the object- expression method walker) passed lambdas or partial applications to the non-inline List HOFs, allocating a closure per call over typically tiny (often empty) lists. Route each through the inline ListInline.foldBack / ListInline.fold twins, whose InlineIfLambda folder is inlined rather than allocated. ListInline.fold is added alongside the existing ListInline.foldBack (inline, InlineIfLambda folder, left-to-right cursor loop). Fold order is unchanged. Extends #20385. IL TypeDefinition enumeration of FSharp.Compiler.Service.dll: the matching per-call closure classes drop from 22 to 6, with the 6 survivors being continuation-passing closures, Option.foldBack partial applications, a LetRec cache-compute thunk, and a List.foldBack2 lambda, none of which is a List fold this technique can inline. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
abonie
force-pushed
the
t-gro-freevars-inline-foldback
branch
from
September 4, 2026 13:13
198fdf4 to
e4f972f
Compare
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.
Extends #20385: routes the remaining lambda / partial-application
List.foldBack/List.foldsites in the free-variable walkers through the inlineListInline.foldBack/ListInline.foldtwins, whoseInlineIfLambdafolder is inlined instead of allocated as a per-call closure. Fold order unchanged. AddsListInline.foldnext to the existingListInline.foldBack.Per-call closure classes in
FSharp.Compiler.Service.dll(ILTypeDefinitionenumeration): 22 → 6. The 6 survivors are CPS continuations, anOption.foldBack, aLetReccache thunk and aList.foldBack2lambda — none is aListfold this inlines.Closure allocation removed, measured over a full FSharp.Compiler.Service self-compile (455 source files, GCAllocationTick sampling, 6 compiles; every class below reaches 0 samples after, matching the IL count):
accFreeInFlatExprs@boundTypars@accFreeInTarget@accFreeInMethod@accFreeInMeasure@,boundTyparsLeftToRight@andaccFreeInTyparConstraintsLeftToRight@are eliminated too (part of the 22→6 IL count) but sit on the units-of-measure / signature-only left-to-right paths, which barely execute while compiling the compiler's own sources, so they don't register on this workload.