From ce9ed2897daf1181da27b91a375674868cfd9002 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Tue, 25 Aug 2026 17:17:59 +0200 Subject: [PATCH] Use a mutable Dictionary for Detuple usage analysis Detuple.GlobalUsageAnalysis threads a Uses map (Zmap) through a per-file fold; logUse does tryFind+add on every value occurrence, so a value used K times re-inserts at the same key K times, each path-copying the whole balanced tree. This is the largest MapTree.mk allocation source in the optimizer. A per-file mutable Dictionary (BagAdd) removes the churn; Uses is only grown during the sequential per-file fold and read back by point lookup or one enumeration sorted by (valSourceOrderKey, Stamp), so its iteration order never reaches output. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 69e8646f-b2d9-4852-af5c-5f96dc2d36a3 --- src/Compiler/Optimize/DetupleArgs.fs | 33 ++++++++++--------- src/Compiler/Optimize/DetupleArgs.fsi | 3 +- .../Optimize/InnerLambdasToTopLevelFuncs.fs | 6 ++-- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/Compiler/Optimize/DetupleArgs.fs b/src/Compiler/Optimize/DetupleArgs.fs index faa83b4b016..72158b2d6b8 100644 --- a/src/Compiler/Optimize/DetupleArgs.fs +++ b/src/Compiler/Optimize/DetupleArgs.fs @@ -2,6 +2,7 @@ module internal FSharp.Compiler.Detuple +open System.Collections.Generic open Internal.Utilities.Collections open Internal.Utilities.Library open FSharp.Compiler.DiagnosticsLogger @@ -173,6 +174,9 @@ module GlobalUsageAnalysis = type accessor = TupleGet of int * TType list + let valStampEquality = + HashIdentity.FromFunctions (fun (v: Val) -> int v.Stamp) (fun (v1: Val) v2 -> v1.Stamp = v2.Stamp) + /// Expr information. /// For each v, /// (a) log it's usage site context = accessors // APP type-inst args @@ -181,7 +185,7 @@ module GlobalUsageAnalysis = type Results = { /// v -> context / APP inst args - Uses: Zmap + Uses: Dictionary /// v -> binding repr Defns: Zmap @@ -197,8 +201,9 @@ module GlobalUsageAnalysis = IterationIsAtTopLevel: bool } - let z0 = - { Uses = Zmap.empty valOrder + /// New instance per file: Uses is mutable and implementation files are optimized in parallel. + let mkInitialResults () = + { Uses = Dictionary(valStampEquality) Defns = Zmap.empty valOrder RecursiveBindings = Zmap.empty valOrder DecisionTreeBindings = Zset.empty valOrder @@ -208,11 +213,8 @@ module GlobalUsageAnalysis = /// Log the use of a value with a particular tuple shape at a callsite /// Note: this routine is called very frequently let logUse (f: Val) tup z = - { z with - Uses = - match Zmap.tryFind f z.Uses with - | Some sites -> Zmap.add f (tup :: sites) z.Uses - | None -> Zmap.add f [ tup ] z.Uses } + z.Uses.BagAdd(f, tup) + z /// Log the definition of a binding let logBinding z (isInDTree, v) = @@ -342,7 +344,7 @@ module GlobalUsageAnalysis = let GetUsageInfoOfImplFile g expr = let folder = UsageFolders g - let z = FoldImplFile folder z0 expr + let z = FoldImplFile folder (mkInitialResults ()) expr z let internalError str = raise (Failure(str)) @@ -610,9 +612,9 @@ let decideFormalSuggestedCP g z tys vss = TupleTS tss let trimTsByVal z ts v = - match Zmap.tryFind v z.Uses with - | None -> UnknownTS (* formal has no usage info, it is unused *) - | Some sites -> + match z.Uses.TryGetValue v with + | false, _ -> UnknownTS (* formal has no usage info, it is unused *) + | true, sites -> let trim ts (accessors, _inst, _args) = trimTsByAccess accessors ts List.fold trim ts sites @@ -703,9 +705,10 @@ let determineTransforms (scope: PerFileNamingScope) g (z: Results) = decideTransform scope g z f callPatterns (m, tps, vss, retTy) // make transform (if required) let vtransforms = - Zmap.toList z.Uses - |> List.sortBy (fst >> valSourceOrderKey) - |> List.choose (fun (f, sites) -> selectTransform f sites) + z.Uses + |> Seq.sortBy (fun (KeyValue(f, _)) -> struct (valSourceOrderKey f, f.Stamp)) + |> Seq.choose (fun (KeyValue(f, sites)) -> selectTransform f sites) + |> List.ofSeq let vtransforms = Zmap.ofList valOrder vtransforms vtransforms diff --git a/src/Compiler/Optimize/DetupleArgs.fsi b/src/Compiler/Optimize/DetupleArgs.fsi index 787a3cfb688..687f91dc31e 100644 --- a/src/Compiler/Optimize/DetupleArgs.fsi +++ b/src/Compiler/Optimize/DetupleArgs.fsi @@ -3,6 +3,7 @@ module internal FSharp.Compiler.Detuple open Internal.Utilities.Collections +open System.Collections.Generic open FSharp.Compiler.CompilerGlobalState open FSharp.Compiler.TcGlobals open FSharp.Compiler.TypedTree @@ -17,7 +18,7 @@ module GlobalUsageAnalysis = type Results = { /// v -> context / APP inst args - Uses: Zmap + Uses: Dictionary /// v -> binding repr Defns: Zmap diff --git a/src/Compiler/Optimize/InnerLambdasToTopLevelFuncs.fs b/src/Compiler/Optimize/InnerLambdasToTopLevelFuncs.fs index 24174f11a45..0b706b34752 100644 --- a/src/Compiler/Optimize/InnerLambdasToTopLevelFuncs.fs +++ b/src/Compiler/Optimize/InnerLambdasToTopLevelFuncs.fs @@ -201,9 +201,9 @@ let IsMandatoryNonTopLevel g (f: Val) = module Pass1_DetermineTLRAndArities = let GetMaxNumArgsAtUses xinfo f = - match Zmap.tryFind f xinfo.Uses with - | None -> 0 (* no call sites *) - | Some sites -> + match xinfo.Uses.TryGetValue f with + | false, _ -> 0 (* no call sites *) + | true, sites -> sites |> List.map (fun (_accessors, _tinst, args) -> List.length args) |> List.max let SelectTLRVals amap g xinfo f e =