From 9f21d518dd0345940a4f614c5538be3b2c64d894 Mon Sep 17 00:00:00 2001 From: Victor Baybekov Date: Wed, 6 Jan 2021 23:41:35 +0100 Subject: [PATCH 1/6] Map: Optimize away `ininst` check Store height in leaves. Compared to the old discussion, when Left/Right were proposed to be stored in a universal node, this adds 4 bytes to leaves or 2 bytes per item on average (vs 16/8). --- src/fsharp/FSharp.Core/map.fs | 243 ++++++++++++++++++++-------------- 1 file changed, 140 insertions(+), 103 deletions(-) diff --git a/src/fsharp/FSharp.Core/map.fs b/src/fsharp/FSharp.Core/map.fs index c0f28b9715a..92338fd63bf 100644 --- a/src/fsharp/FSharp.Core/map.fs +++ b/src/fsharp/FSharp.Core/map.fs @@ -5,25 +5,27 @@ namespace Microsoft.FSharp.Collections open System open System.Collections.Generic open System.Diagnostics +open System.Runtime.CompilerServices open System.Text open Microsoft.FSharp.Core open Microsoft.FSharp.Core.LanguagePrimitives.IntrinsicOperators [] [] -type internal MapTree<'Key, 'Value>(k: 'Key, v: 'Value) = +type internal MapTree<'Key, 'Value>(k: 'Key, v: 'Value, h: int) = + member _.Height = h member _.Key = k member _.Value = v - + new(k: 'Key, v: 'Value) = MapTree(k,v,1) + [] [] [] type internal MapTreeNode<'Key, 'Value>(k:'Key, v:'Value, left:MapTree<'Key, 'Value>, right: MapTree<'Key, 'Value>, h: int) = - inherit MapTree<'Key,'Value>(k, v) - + inherit MapTree<'Key,'Value>(k, v, h) member _.Left = left member _.Right = right - member _.Height = h + [] module MapTree = @@ -32,14 +34,17 @@ module MapTree = let inline isEmpty (m:MapTree<'Key, 'Value>) = isNull m + let inline private asNode(value:MapTree<'Key,'Value>) : MapTreeNode<'Key,'Value> = + value :?> MapTreeNode<'Key,'Value> + let rec sizeAux acc (m:MapTree<'Key, 'Value>) = if isEmpty m then acc else - match m with - | :? MapTreeNode<'Key, 'Value> as mn -> sizeAux (sizeAux (acc+1) mn.Left) mn.Right - | _ -> acc + 1 - + match m.Height with + | 1 -> acc + 1 + | _ -> let mn = asNode m in sizeAux (sizeAux (acc+1) mn.Left) mn.Right + let size x = sizeAux 0 x #if TRACE_SETS_AND_MAPS @@ -82,10 +87,7 @@ module MapTree = let inline height (m: MapTree<'Key, 'Value>) = if isEmpty m then 0 - else - match m with - | :? MapTreeNode<'Key, 'Value> as mn -> mn.Height - | _ -> 1 + else m.Height [] let tolerance = 2 @@ -98,9 +100,6 @@ module MapTree = MapTree(k,v) else MapTreeNode(k,v,l,r,m+1) :> MapTree<'Key, 'Value> // new map is higher by 1 than the highest - - let inline private asNode(value:MapTree<'Key,'Value>) : MapTreeNode<'Key,'Value> = - value :?> MapTreeNode<'Key,'Value> let rebalance t1 (k: 'Key) (v: 'Value) t2 : MapTree<'Key, 'Value> = let t1h = height t1 @@ -129,33 +128,39 @@ module MapTree = if isEmpty m then MapTree(k,v) else let c = comparer.Compare(k,m.Key) - match m with - | :? MapTreeNode<'Key, 'Value> as mn -> - if c < 0 then rebalance (add comparer k v mn.Left) mn.Key mn.Value mn.Right - elif c = 0 then MapTreeNode(k,v,mn.Left,mn.Right,mn.Height) :> MapTree<'Key, 'Value> - else rebalance mn.Left mn.Key mn.Value (add comparer k v mn.Right) - | _ -> + match m.Height with + | 1 -> if c < 0 then MapTreeNode (k,v,empty,m,2) :> MapTree<'Key, 'Value> elif c = 0 then MapTree(k,v) else MapTreeNode (k,v,m,empty,2) :> MapTree<'Key, 'Value> - + | _ -> + let mn = asNode m + if c < 0 then rebalance (add comparer k v mn.Left) mn.Key mn.Value mn.Right + elif c = 0 then MapTreeNode(k,v,mn.Left,mn.Right,mn.Height) :> MapTree<'Key, 'Value> + else rebalance mn.Left mn.Key mn.Value (add comparer k v mn.Right) + let rec tryGetValue (comparer: IComparer<'Key>) k (v: byref<'Value>) (m: MapTree<'Key, 'Value>) = if isEmpty m then false else let c = comparer.Compare(k, m.Key) if c = 0 then v <- m.Value; true else - match m with - | :? MapTreeNode<'Key, 'Value> as mn -> + match m.Height with + | 1 -> false + | _ -> + let mn = asNode m tryGetValue comparer k &v (if c < 0 then mn.Left else mn.Right) - | _ -> false - + + [] + let throwKeyNotFound() = raise (KeyNotFoundException()) + + [] let find (comparer: IComparer<'Key>) k (m: MapTree<'Key, 'Value>) = let mutable v = Unchecked.defaultof<'Value> if tryGetValue comparer k &v m then v else - raise (KeyNotFoundException()) + throwKeyNotFound() let tryFind (comparer: IComparer<'Key>) k (m: MapTree<'Key, 'Value>) = let mutable v = Unchecked.defaultof<'Value> @@ -170,12 +175,14 @@ module MapTree = let rec partitionAux (comparer: IComparer<'Key>) (f: OptimizedClosures.FSharpFunc<_, _, _>) (m: MapTree<'Key, 'Value>) acc = if isEmpty m then acc else - match m with - | :? MapTreeNode<'Key, 'Value> as mn -> + match m.Height with + | 1 -> partition1 comparer f m.Key m.Value acc + | _ -> + let mn = asNode m let acc = partitionAux comparer f mn.Right acc let acc = partition1 comparer f mn.Key mn.Value acc partitionAux comparer f mn.Left acc - | _ -> partition1 comparer f m.Key m.Value acc + let partition (comparer: IComparer<'Key>) f m = partitionAux comparer (OptimizedClosures.FSharpFunc<_, _, _>.Adapt f) m (empty, empty) @@ -186,12 +193,14 @@ module MapTree = let rec filterAux (comparer: IComparer<'Key>) (f: OptimizedClosures.FSharpFunc<_, _, _>) (m: MapTree<'Key, 'Value>) acc = if isEmpty m then acc else - match m with - | :? MapTreeNode<'Key, 'Value> as mn -> + match m.Height with + | 1 -> filter1 comparer f m.Key m.Value acc + | _ -> + let mn = asNode m let acc = filterAux comparer f mn.Left acc let acc = filter1 comparer f mn.Key mn.Value acc filterAux comparer f mn.Right acc - | _ -> filter1 comparer f m.Key m.Value acc + let filter (comparer: IComparer<'Key>) f m = filterAux comparer (OptimizedClosures.FSharpFunc<_, _, _>.Adapt f) m empty @@ -199,18 +208,21 @@ module MapTree = let rec spliceOutSuccessor (m: MapTree<'Key, 'Value>) = if isEmpty m then failwith "internal error: Map.spliceOutSuccessor" else - match m with - | :? MapTreeNode<'Key, 'Value> as mn -> + match m.Height with + | 1 -> m.Key, m.Value, empty + | _ -> + let mn = asNode m if isEmpty mn.Left then mn.Key, mn.Value, mn.Right else let k3, v3, l' = spliceOutSuccessor mn.Left in k3, v3, mk l' mn.Key mn.Value mn.Right - | _ -> m.Key, m.Value, empty let rec remove (comparer: IComparer<'Key>) k (m: MapTree<'Key, 'Value>) = if isEmpty m then empty else let c = comparer.Compare(k, m.Key) - match m with - | :? MapTreeNode<'Key, 'Value> as mn -> + match m.Height with + | 1 -> if c = 0 then empty else m + | _ -> + let mn = asNode m if c < 0 then rebalance (remove comparer k mn.Left) mn.Key mn.Value mn.Right elif c = 0 then if isEmpty mn.Left then mn.Right @@ -219,8 +231,7 @@ module MapTree = let sk, sv, r' = spliceOutSuccessor mn.Right mk mn.Left sk sv r' else rebalance mn.Left mn.Key mn.Value (remove comparer k mn.Right) - | _ -> - if c = 0 then empty else m + let rec change (comparer: IComparer<'Key>) k (u: 'Value option -> 'Value option) (m: MapTree<'Key, 'Value>) : MapTree<'Key,'Value> = if isEmpty m then @@ -228,8 +239,23 @@ module MapTree = | None -> m | Some v -> MapTree (k, v) else - match m with - | :? MapTreeNode<'Key, 'Value> as mn -> + match m.Height with + | 1 -> + let c = comparer.Compare(k, m.Key) + if c < 0 then + match u None with + | None -> m + | Some v -> MapTreeNode (k, v, empty, m, 2) :> MapTree<'Key,'Value> + elif c = 0 then + match u (Some m.Value) with + | None -> empty + | Some v -> MapTree (k, v) + else + match u None with + | None -> m + | Some v -> MapTreeNode (k, v, m, empty, 2) :> MapTree<'Key,'Value> + | _ -> + let mn = asNode m let c = comparer.Compare(k, mn.Key) if c < 0 then rebalance (change comparer k u mn.Left) mn.Key mn.Value mn.Right @@ -244,37 +270,28 @@ module MapTree = | Some v -> MapTreeNode (k, v, mn.Left, mn.Right, mn.Height) :> MapTree<'Key,'Value> else rebalance mn.Left mn.Key mn.Value (change comparer k u mn.Right) - | _ -> - let c = comparer.Compare(k, m.Key) - if c < 0 then - match u None with - | None -> m - | Some v -> MapTreeNode (k, v, empty, m, 2) :> MapTree<'Key,'Value> - elif c = 0 then - match u (Some m.Value) with - | None -> empty - | Some v -> MapTree (k, v) - else - match u None with - | None -> m - | Some v -> MapTreeNode (k, v, m, empty, 2) :> MapTree<'Key,'Value> let rec mem (comparer: IComparer<'Key>) k (m: MapTree<'Key, 'Value>) = if isEmpty m then false else let c = comparer.Compare(k, m.Key) - match m with - | :? MapTreeNode<'Key, 'Value> as mn -> + match m.Height with + | 1 -> c = 0 + | _ -> + let mn = asNode m if c < 0 then mem comparer k mn.Left else (c = 0 || mem comparer k mn.Right) - | _ -> c = 0 + let rec iterOpt (f: OptimizedClosures.FSharpFunc<_, _, _>) (m: MapTree<'Key, 'Value>) = if isEmpty m then () else - match m with - | :? MapTreeNode<'Key, 'Value> as mn -> iterOpt f mn.Left; f.Invoke (mn.Key, mn.Value); iterOpt f mn.Right - | _ -> f.Invoke (m.Key, m.Value) + match m.Height with + | 1 -> f.Invoke (m.Key, m.Value) + | _ -> + let mn = asNode m + iterOpt f mn.Left; f.Invoke (mn.Key, mn.Value); iterOpt f mn.Right + let iter f m = iterOpt (OptimizedClosures.FSharpFunc<_, _, _>.Adapt f) m @@ -282,8 +299,10 @@ module MapTree = let rec tryPickOpt (f: OptimizedClosures.FSharpFunc<_, _, _>) (m: MapTree<'Key, 'Value>) = if isEmpty m then None else - match m with - | :? MapTreeNode<'Key, 'Value> as mn -> + match m.Height with + | 1 -> f.Invoke (m.Key, m.Value) + | _ -> + let mn = asNode m match tryPickOpt f mn.Left with | Some _ as res -> res | None -> @@ -291,7 +310,7 @@ module MapTree = | Some _ as res -> res | None -> tryPickOpt f mn.Right - | _ -> f.Invoke (m.Key, m.Value) + let tryPick f m = tryPickOpt (OptimizedClosures.FSharpFunc<_, _, _>.Adapt f) m @@ -299,9 +318,12 @@ module MapTree = let rec existsOpt (f: OptimizedClosures.FSharpFunc<_, _, _>) (m: MapTree<'Key, 'Value>) = if isEmpty m then false else - match m with - | :? MapTreeNode<'Key, 'Value> as mn -> existsOpt f mn.Left || f.Invoke (mn.Key, mn.Value) || existsOpt f mn.Right - | _ -> f.Invoke (m.Key, m.Value) + match m.Height with + | 1 -> f.Invoke (m.Key, m.Value) + | _ -> + let mn = asNode m + existsOpt f mn.Left || f.Invoke (mn.Key, mn.Value) || existsOpt f mn.Right + let exists f m = existsOpt (OptimizedClosures.FSharpFunc<_, _, _>.Adapt f) m @@ -309,9 +331,12 @@ module MapTree = let rec forallOpt (f: OptimizedClosures.FSharpFunc<_, _, _>) (m: MapTree<'Key, 'Value>) = if isEmpty m then true else - match m with - | :? MapTreeNode<'Key, 'Value> as mn -> forallOpt f mn.Left && f.Invoke (mn.Key, mn.Value) && forallOpt f mn.Right - | _ -> f.Invoke (m.Key, m.Value) + match m.Height with + | 1 -> f.Invoke (m.Key, m.Value) + | _ -> + let mn = asNode m + forallOpt f mn.Left && f.Invoke (mn.Key, mn.Value) && forallOpt f mn.Right + let forall f m = @@ -320,24 +345,27 @@ module MapTree = let rec map (f:'Value -> 'Result) (m: MapTree<'Key, 'Value>) : MapTree<'Key, 'Result> = if isEmpty m then empty else - match m with - | :? MapTreeNode<'Key, 'Value> as mn -> + match m.Height with + | 1 -> MapTree (m.Key, f m.Value) + | _ -> + let mn = asNode m let l2 = map f mn.Left let v2 = f mn.Value let r2 = map f mn.Right MapTreeNode (mn.Key, v2, l2, r2, mn.Height) :> MapTree<'Key, 'Result> - | _ -> MapTree (m.Key, f m.Value) let rec mapiOpt (f: OptimizedClosures.FSharpFunc<'Key, 'Value, 'Result>) (m: MapTree<'Key, 'Value>) = if isEmpty m then empty else - match m with - | :? MapTreeNode<'Key, 'Value> as mn -> + match m.Height with + | 1 -> MapTree (m.Key, f.Invoke (m.Key, m.Value)) + | _ -> + let mn = asNode m let l2 = mapiOpt f mn.Left let v2 = f.Invoke (mn.Key, mn.Value) let r2 = mapiOpt f mn.Right MapTreeNode (mn.Key, v2, l2, r2, mn.Height) :> MapTree<'Key, 'Result> - | _ -> MapTree (m.Key, f.Invoke (m.Key, m.Value)) + let mapi f m = mapiOpt (OptimizedClosures.FSharpFunc<_, _, _>.Adapt f) m @@ -345,12 +373,14 @@ module MapTree = let rec foldBackOpt (f: OptimizedClosures.FSharpFunc<_, _, _, _>) (m: MapTree<'Key, 'Value>) x = if isEmpty m then x else - match m with - | :? MapTreeNode<'Key, 'Value> as mn -> + match m.Height with + | 1 -> f.Invoke (m.Key, m.Value, x) + | _ -> + let mn = asNode m let x = foldBackOpt f mn.Right x let x = f.Invoke (mn.Key, mn.Value, x) foldBackOpt f mn.Left x - | _ -> f.Invoke (m.Key, m.Value, x) + let foldBack f m x = foldBackOpt (OptimizedClosures.FSharpFunc<_, _, _, _>.Adapt f) m x @@ -358,12 +388,13 @@ module MapTree = let rec foldOpt (f: OptimizedClosures.FSharpFunc<_, _, _, _>) x (m: MapTree<'Key, 'Value>) = if isEmpty m then x else - match m with - | :? MapTreeNode<'Key, 'Value> as mn -> + match m.Height with + | 1 -> f.Invoke (x, m.Key, m.Value) + | _ -> + let mn = asNode m let x = foldOpt f x mn.Left let x = f.Invoke (x, mn.Key, mn.Value) foldOpt f x mn.Right - | _ -> f.Invoke (x, m.Key, m.Value) let fold f x m = foldOpt (OptimizedClosures.FSharpFunc<_, _, _, _>.Adapt f) x m @@ -372,19 +403,20 @@ module MapTree = let rec foldFromTo (f: OptimizedClosures.FSharpFunc<_, _, _, _>) (m: MapTree<'Key, 'Value>) x = if isEmpty m then x else - match m with - | :? MapTreeNode<'Key, 'Value> as mn -> + match m.Height with + | 1 -> + let cLoKey = comparer.Compare(lo, m.Key) + let cKeyHi = comparer.Compare(m.Key, hi) + let x = if cLoKey <= 0 && cKeyHi <= 0 then f.Invoke (m.Key, m.Value, x) else x + x + | _ -> + let mn = asNode m let cLoKey = comparer.Compare(lo, mn.Key) let cKeyHi = comparer.Compare(mn.Key, hi) let x = if cLoKey < 0 then foldFromTo f mn.Left x else x let x = if cLoKey <= 0 && cKeyHi <= 0 then f.Invoke (mn.Key, mn.Value, x) else x let x = if cKeyHi < 0 then foldFromTo f mn.Right x else x x - | _ -> - let cLoKey = comparer.Compare(lo, m.Key) - let cKeyHi = comparer.Compare(m.Key, hi) - let x = if cLoKey <= 0 && cKeyHi <= 0 then f.Invoke (m.Key, m.Value, x) else x - x if comparer.Compare(lo, hi) = 1 then x else foldFromTo f m x @@ -395,9 +427,12 @@ module MapTree = let rec loop (m: MapTree<'Key, 'Value>) acc = if isEmpty m then acc else - match m with - | :? MapTreeNode<'Key, 'Value> as mn -> loop mn.Left ((mn.Key, mn.Value) :: loop mn.Right acc) - | _ -> (m.Key, m.Value) :: acc + match m.Height with + | 1 -> (m.Key, m.Value) :: acc + | _ -> + let mn = asNode m + loop mn.Left ((mn.Key, mn.Value) :: loop mn.Right acc) + loop m [] let toArray m = @@ -448,9 +483,11 @@ module MapTree = | m :: rest -> if isEmpty m then collapseLHS rest else - match m with - | :? MapTreeNode<'Key, 'Value> as mn -> collapseLHS (mn.Left :: MapTree (mn.Key, mn.Value) :: mn.Right :: rest) - | _ -> stack + match m.Height with + | 1 -> stack + | _ -> + let mn = asNode m + collapseLHS (mn.Left :: MapTree (mn.Key, mn.Value) :: mn.Right :: rest) let mkIterator m = { stack = collapseLHS [m]; started = false } @@ -466,9 +503,9 @@ module MapTree = match i.stack with | [] -> alreadyFinished() | m :: _ -> - match m with - | :? MapTreeNode<'Key, 'Value> -> failwith "Please report error: Map iterator, unexpected stack for current" - | _ -> new KeyValuePair<_, _>(m.Key, m.Value) + match m.Height with + | 1 -> new KeyValuePair<_, _>(m.Key, m.Value) + | _ -> failwith "Please report error: Map iterator, unexpected stack for current" else notStarted() @@ -477,11 +514,11 @@ module MapTree = match i.stack with | [] -> false | m :: rest -> - match m with - | :? MapTreeNode<'Key, 'Value> -> failwith "Please report error: Map iterator, unexpected stack for moveNext" - | _ -> + match m.Height with + | 1 -> i.stack <- collapseLHS rest not i.stack.IsEmpty + | _ -> failwith "Please report error: Map iterator, unexpected stack for moveNext" else i.started <- true (* The first call to MoveNext "starts" the enumeration. *) not i.stack.IsEmpty From aa0572c18e1cb07f86f786cbb9c60e3cf5272de5 Mon Sep 17 00:00:00 2001 From: Victor Baybekov Date: Thu, 7 Jan 2021 13:55:44 +0100 Subject: [PATCH 2/6] Map: use `if` instead of `match` `Match` produces `sub 1` and `switch` instruction. Here, for any non-trivial count, nodes are more frequent than leaves on the path, so branch prediction should be beneficial. --- src/fsharp/FSharp.Core/map.fs | 135 +++++++++++++++++----------------- 1 file changed, 68 insertions(+), 67 deletions(-) diff --git a/src/fsharp/FSharp.Core/map.fs b/src/fsharp/FSharp.Core/map.fs index 92338fd63bf..3f657c5d94b 100644 --- a/src/fsharp/FSharp.Core/map.fs +++ b/src/fsharp/FSharp.Core/map.fs @@ -41,9 +41,11 @@ module MapTree = if isEmpty m then acc else - match m.Height with - | 1 -> acc + 1 - | _ -> let mn = asNode m in sizeAux (sizeAux (acc+1) mn.Left) mn.Right + if m.Height = 1 then + acc + 1 + else + let mn = asNode m + sizeAux (sizeAux (acc+1) mn.Left) mn.Right let size x = sizeAux 0 x @@ -128,12 +130,11 @@ module MapTree = if isEmpty m then MapTree(k,v) else let c = comparer.Compare(k,m.Key) - match m.Height with - | 1 -> + if m.Height = 1 then if c < 0 then MapTreeNode (k,v,empty,m,2) :> MapTree<'Key, 'Value> elif c = 0 then MapTree(k,v) else MapTreeNode (k,v,m,empty,2) :> MapTree<'Key, 'Value> - | _ -> + else let mn = asNode m if c < 0 then rebalance (add comparer k v mn.Left) mn.Key mn.Value mn.Right elif c = 0 then MapTreeNode(k,v,mn.Left,mn.Right,mn.Height) :> MapTree<'Key, 'Value> @@ -145,9 +146,8 @@ module MapTree = let c = comparer.Compare(k, m.Key) if c = 0 then v <- m.Value; true else - match m.Height with - | 1 -> false - | _ -> + if m.Height = 1 then false + else let mn = asNode m tryGetValue comparer k &v (if c < 0 then mn.Left else mn.Right) @@ -175,15 +175,14 @@ module MapTree = let rec partitionAux (comparer: IComparer<'Key>) (f: OptimizedClosures.FSharpFunc<_, _, _>) (m: MapTree<'Key, 'Value>) acc = if isEmpty m then acc else - match m.Height with - | 1 -> partition1 comparer f m.Key m.Value acc - | _ -> + if m.Height = 1 then + partition1 comparer f m.Key m.Value acc + else let mn = asNode m let acc = partitionAux comparer f mn.Right acc let acc = partition1 comparer f mn.Key mn.Value acc partitionAux comparer f mn.Left acc - let partition (comparer: IComparer<'Key>) f m = partitionAux comparer (OptimizedClosures.FSharpFunc<_, _, _>.Adapt f) m (empty, empty) @@ -193,9 +192,9 @@ module MapTree = let rec filterAux (comparer: IComparer<'Key>) (f: OptimizedClosures.FSharpFunc<_, _, _>) (m: MapTree<'Key, 'Value>) acc = if isEmpty m then acc else - match m.Height with - | 1 -> filter1 comparer f m.Key m.Value acc - | _ -> + if m.Height = 1 then + filter1 comparer f m.Key m.Value acc + else let mn = asNode m let acc = filterAux comparer f mn.Left acc let acc = filter1 comparer f mn.Key mn.Value acc @@ -208,9 +207,9 @@ module MapTree = let rec spliceOutSuccessor (m: MapTree<'Key, 'Value>) = if isEmpty m then failwith "internal error: Map.spliceOutSuccessor" else - match m.Height with - | 1 -> m.Key, m.Value, empty - | _ -> + if m.Height = 1 then + m.Key, m.Value, empty + else let mn = asNode m if isEmpty mn.Left then mn.Key, mn.Value, mn.Right else let k3, v3, l' = spliceOutSuccessor mn.Left in k3, v3, mk l' mn.Key mn.Value mn.Right @@ -219,9 +218,9 @@ module MapTree = if isEmpty m then empty else let c = comparer.Compare(k, m.Key) - match m.Height with - | 1 -> if c = 0 then empty else m - | _ -> + if m.Height = 1 then + if c = 0 then empty else m + else let mn = asNode m if c < 0 then rebalance (remove comparer k mn.Left) mn.Key mn.Value mn.Right elif c = 0 then @@ -239,8 +238,7 @@ module MapTree = | None -> m | Some v -> MapTree (k, v) else - match m.Height with - | 1 -> + if m.Height = 1 then let c = comparer.Compare(k, m.Key) if c < 0 then match u None with @@ -254,7 +252,7 @@ module MapTree = match u None with | None -> m | Some v -> MapTreeNode (k, v, m, empty, 2) :> MapTree<'Key,'Value> - | _ -> + else let mn = asNode m let c = comparer.Compare(k, mn.Key) if c < 0 then @@ -275,9 +273,9 @@ module MapTree = if isEmpty m then false else let c = comparer.Compare(k, m.Key) - match m.Height with - | 1 -> c = 0 - | _ -> + if m.Height = 1 then + c = 0 + else let mn = asNode m if c < 0 then mem comparer k mn.Left else (c = 0 || mem comparer k mn.Right) @@ -286,9 +284,9 @@ module MapTree = let rec iterOpt (f: OptimizedClosures.FSharpFunc<_, _, _>) (m: MapTree<'Key, 'Value>) = if isEmpty m then () else - match m.Height with - | 1 -> f.Invoke (m.Key, m.Value) - | _ -> + if m.Height = 1 then + f.Invoke (m.Key, m.Value) + else let mn = asNode m iterOpt f mn.Left; f.Invoke (mn.Key, mn.Value); iterOpt f mn.Right @@ -299,9 +297,9 @@ module MapTree = let rec tryPickOpt (f: OptimizedClosures.FSharpFunc<_, _, _>) (m: MapTree<'Key, 'Value>) = if isEmpty m then None else - match m.Height with - | 1 -> f.Invoke (m.Key, m.Value) - | _ -> + if m.Height = 1 then + f.Invoke (m.Key, m.Value) + else let mn = asNode m match tryPickOpt f mn.Left with | Some _ as res -> res @@ -318,9 +316,9 @@ module MapTree = let rec existsOpt (f: OptimizedClosures.FSharpFunc<_, _, _>) (m: MapTree<'Key, 'Value>) = if isEmpty m then false else - match m.Height with - | 1 -> f.Invoke (m.Key, m.Value) - | _ -> + if m.Height = 1 then + f.Invoke (m.Key, m.Value) + else let mn = asNode m existsOpt f mn.Left || f.Invoke (mn.Key, mn.Value) || existsOpt f mn.Right @@ -331,9 +329,9 @@ module MapTree = let rec forallOpt (f: OptimizedClosures.FSharpFunc<_, _, _>) (m: MapTree<'Key, 'Value>) = if isEmpty m then true else - match m.Height with - | 1 -> f.Invoke (m.Key, m.Value) - | _ -> + if m.Height = 1 then + f.Invoke (m.Key, m.Value) + else let mn = asNode m forallOpt f mn.Left && f.Invoke (mn.Key, mn.Value) && forallOpt f mn.Right @@ -345,9 +343,9 @@ module MapTree = let rec map (f:'Value -> 'Result) (m: MapTree<'Key, 'Value>) : MapTree<'Key, 'Result> = if isEmpty m then empty else - match m.Height with - | 1 -> MapTree (m.Key, f m.Value) - | _ -> + if m.Height = 1 then + MapTree (m.Key, f m.Value) + else let mn = asNode m let l2 = map f mn.Left let v2 = f mn.Value @@ -357,9 +355,9 @@ module MapTree = let rec mapiOpt (f: OptimizedClosures.FSharpFunc<'Key, 'Value, 'Result>) (m: MapTree<'Key, 'Value>) = if isEmpty m then empty else - match m.Height with - | 1 -> MapTree (m.Key, f.Invoke (m.Key, m.Value)) - | _ -> + if m.Height = 1 then + MapTree (m.Key, f.Invoke (m.Key, m.Value)) + else let mn = asNode m let l2 = mapiOpt f mn.Left let v2 = f.Invoke (mn.Key, mn.Value) @@ -373,9 +371,9 @@ module MapTree = let rec foldBackOpt (f: OptimizedClosures.FSharpFunc<_, _, _, _>) (m: MapTree<'Key, 'Value>) x = if isEmpty m then x else - match m.Height with - | 1 -> f.Invoke (m.Key, m.Value, x) - | _ -> + if m.Height = 1 then + f.Invoke (m.Key, m.Value, x) + else let mn = asNode m let x = foldBackOpt f mn.Right x let x = f.Invoke (mn.Key, mn.Value, x) @@ -388,9 +386,9 @@ module MapTree = let rec foldOpt (f: OptimizedClosures.FSharpFunc<_, _, _, _>) x (m: MapTree<'Key, 'Value>) = if isEmpty m then x else - match m.Height with - | 1 -> f.Invoke (x, m.Key, m.Value) - | _ -> + if m.Height = 1 then + f.Invoke (x, m.Key, m.Value) + else let mn = asNode m let x = foldOpt f x mn.Left let x = f.Invoke (x, mn.Key, mn.Value) @@ -403,13 +401,12 @@ module MapTree = let rec foldFromTo (f: OptimizedClosures.FSharpFunc<_, _, _, _>) (m: MapTree<'Key, 'Value>) x = if isEmpty m then x else - match m.Height with - | 1 -> + if m.Height = 1 then let cLoKey = comparer.Compare(lo, m.Key) let cKeyHi = comparer.Compare(m.Key, hi) let x = if cLoKey <= 0 && cKeyHi <= 0 then f.Invoke (m.Key, m.Value, x) else x x - | _ -> + else let mn = asNode m let cLoKey = comparer.Compare(lo, mn.Key) let cKeyHi = comparer.Compare(mn.Key, hi) @@ -427,9 +424,9 @@ module MapTree = let rec loop (m: MapTree<'Key, 'Value>) acc = if isEmpty m then acc else - match m.Height with - | 1 -> (m.Key, m.Value) :: acc - | _ -> + if m.Height = 1 then + (m.Key, m.Value) :: acc + else let mn = asNode m loop mn.Left ((mn.Key, mn.Value) :: loop mn.Right acc) @@ -483,9 +480,9 @@ module MapTree = | m :: rest -> if isEmpty m then collapseLHS rest else - match m.Height with - | 1 -> stack - | _ -> + if m.Height = 1 then + stack + else let mn = asNode m collapseLHS (mn.Left :: MapTree (mn.Key, mn.Value) :: mn.Right :: rest) @@ -497,15 +494,20 @@ module MapTree = let alreadyFinished() = raise (InvalidOperationException(SR.GetString(SR.enumerationAlreadyFinished))) + + let unexpectedStackForCurrent() = + failwith "Please report error: Map iterator, unexpected stack for current" + + let unexpectedStackForMoveNext() = + failwith "Please report error: Map iterator, unexpected stack for moveNext" let current i = if i.started then match i.stack with | [] -> alreadyFinished() | m :: _ -> - match m.Height with - | 1 -> new KeyValuePair<_, _>(m.Key, m.Value) - | _ -> failwith "Please report error: Map iterator, unexpected stack for current" + if m.Height = 1 then KeyValuePair<_, _>(m.Key, m.Value) + else unexpectedStackForCurrent() else notStarted() @@ -514,11 +516,10 @@ module MapTree = match i.stack with | [] -> false | m :: rest -> - match m.Height with - | 1 -> + if m.Height = 1 then i.stack <- collapseLHS rest not i.stack.IsEmpty - | _ -> failwith "Please report error: Map iterator, unexpected stack for moveNext" + else unexpectedStackForMoveNext() else i.started <- true (* The first call to MoveNext "starts" the enumeration. *) not i.stack.IsEmpty From bc2825896193c7769980c8ddbaff36e829f8e259 Mon Sep 17 00:00:00 2001 From: Victor Baybekov Date: Sun, 10 Jan 2021 02:03:15 +0100 Subject: [PATCH 3/6] Map: Optimize away `ininst` check: fix trace --- src/fsharp/FSharp.Core/map.fs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/fsharp/FSharp.Core/map.fs b/src/fsharp/FSharp.Core/map.fs index 3f657c5d94b..062bb1424da 100644 --- a/src/fsharp/FSharp.Core/map.fs +++ b/src/fsharp/FSharp.Core/map.fs @@ -73,11 +73,11 @@ module MapTree = (totalSizeOnMapLookup / float numLookups)) System.Console.WriteLine("#largestMapSize = {0}, largestMapStackTrace = {1}", largestMapSize, largestMapStackTrace) - let MapTree n = + let MapTree (k,v) = report() numOnes <- numOnes + 1 totalSizeOnNodeCreation <- totalSizeOnNodeCreation + 1.0 - MapTree n + MapTree (k,v) let MapTreeNode (x, l, v, r, h) = report() From 2ccfed33f2635ff3e98d76d19c3c96022d6d0e67 Mon Sep 17 00:00:00 2001 From: Victor Baybekov Date: Sun, 10 Jan 2021 02:27:35 +0100 Subject: [PATCH 4/6] Set: Optimize away `isinst` check Same as #10845 --- src/fsharp/FSharp.Core/set.fs | 277 ++++++++++++++++++---------------- 1 file changed, 146 insertions(+), 131 deletions(-) diff --git a/src/fsharp/FSharp.Core/set.fs b/src/fsharp/FSharp.Core/set.fs index 23455cb1d44..f09ca1528db 100644 --- a/src/fsharp/FSharp.Core/set.fs +++ b/src/fsharp/FSharp.Core/set.fs @@ -16,19 +16,19 @@ open Microsoft.FSharp.Collections [] [] -type internal SetTree<'T>(k: 'T) = +type internal SetTree<'T>(k: 'T, h: int) = + member _.Height = h member _.Key = k - + new(k: 'T) = SetTree(k,1) + [] [] [] type internal SetTreeNode<'T>(v:'T, left:SetTree<'T>, right: SetTree<'T>, h: int) = - inherit SetTree<'T>(v) - + inherit SetTree<'T>(v,h) member _.Left = left member _.Right = right - member _.Height = h - + [] module internal SetTree = @@ -36,13 +36,18 @@ module internal SetTree = let inline isEmpty (t:SetTree<'T>) = isNull t + let inline private asNode(value:SetTree<'T>) : SetTreeNode<'T> = + value :?> SetTreeNode<'T> + let rec countAux (t:SetTree<'T>) acc = if isEmpty t then acc else - match t with - | :? SetTreeNode<'T> as tn -> countAux tn.Left (countAux tn.Right (acc+1)) - | _ -> acc+1 + if t.Height = 1 then + acc + 1 + else + let tn = asNode t + countAux tn.Left (countAux tn.Right (acc+1)) let count s = countAux s 0 @@ -84,22 +89,19 @@ module internal SetTree = let inline height (t:SetTree<'T>) = if isEmpty t then 0 - else - match t with - | :? SetTreeNode<'T> as tn -> tn.Height - | _ -> 1 + else t.Height #if CHECKED let rec checkInvariant (t:SetTree<'T>) = // A good sanity check, loss of balance can hit perf if isEmpty t then true else - match t with - | :? SetTreeNode<'T> as tn -> + if t.Height = 1 then true + else + let tn = asNode t let h1 = height tn.Left let h2 = height tn.Right (-2 <= (h1 - h2) && (h1 - h2) <= 2) && checkInvariant tn.Left && checkInvariant tn.Right - | _ -> true #endif [] @@ -114,9 +116,6 @@ module internal SetTree = else SetTreeNode (k, l, r, m+1) :> SetTree<'T> - let inline private asNode(value:SetTree<'T>) : SetTreeNode<'T> = - value :?> SetTreeNode<'T> - let rebalance t1 v t2 = let t1h = height t1 let t2h = height t2 @@ -144,17 +143,17 @@ module internal SetTree = if isEmpty t then SetTree k else let c = comparer.Compare(k, t.Key) - match t with - | :? SetTreeNode<'T> as tn -> - if c < 0 then rebalance (add comparer k tn.Left) tn.Key tn.Right - elif c = 0 then t - else rebalance tn.Left tn.Key (add comparer k tn.Right) - | _ -> + if t.Height = 1 then // nb. no check for rebalance needed for small trees, also be sure to reuse node already allocated let c = comparer.Compare(k, t.Key) if c < 0 then SetTreeNode (k, empty, t, 2) :> SetTree<'T> elif c = 0 then t else SetTreeNode (k, t, empty, 2) :> SetTree<'T> + else + let tn = asNode t + if c < 0 then rebalance (add comparer k tn.Left) tn.Key tn.Right + elif c = 0 then t + else rebalance tn.Left tn.Key (add comparer k tn.Right) let rec balance comparer (t1:SetTree<'T>) k (t2:SetTree<'T>) = // Given t1 < k < t2 where t1 and t2 are "balanced", @@ -163,10 +162,12 @@ module internal SetTree = if isEmpty t1 then add comparer k t2 // drop t1 = empty elif isEmpty t2 then add comparer k t1 // drop t2 = empty else - match t1 with - | :? SetTreeNode<'T> as t1n -> - match t2 with - | :? SetTreeNode<'T> as t2n -> + if t1.Height = 1 then add comparer k (add comparer t1.Key t2) + else + let t1n = asNode t1 + if t2.Height = 1 then add comparer k (add comparer t2.Key t1) + else + let t2n = asNode t2 // Have: (t1l < k1 < t1r) < k < (t2l < k2 < t2r) // Either (a) h1, h2 differ by at most 2 - no rebalance needed. // (b) h1 too small, i.e. h1+2 < h2 @@ -182,16 +183,19 @@ module internal SetTree = else // case: a, h1 and h2 meet balance requirement mk t1 k t2 - | _ -> add comparer k (add comparer t2.Key t1) - | _ -> add comparer k (add comparer t1.Key t2) let rec split (comparer: IComparer<'T>) pivot (t:SetTree<'T>) = // Given a pivot and a set t // Return { x in t s.t. x < pivot }, pivot in t?, { x in t s.t. x > pivot } if isEmpty t then empty, false, empty else - match t with - | :? SetTreeNode<'T> as tn -> + if t.Height = 1 then + let c = comparer.Compare(t.Key, pivot) + if c < 0 then t, false, empty // singleton under pivot + elif c = 0 then empty, true, empty // singleton is pivot + else empty, false, t // singleton over pivot + else + let tn = asNode t let c = comparer.Compare(pivot, tn.Key) if c < 0 then // pivot t1 let t11Lo, havePivot, t11Hi = split comparer pivot tn.Left @@ -201,27 +205,24 @@ module internal SetTree = else // pivot t2 let t12Lo, havePivot, t12Hi = split comparer pivot tn.Right balance comparer tn.Left tn.Key t12Lo, havePivot, t12Hi - | _ -> - let c = comparer.Compare(t.Key, pivot) - if c < 0 then t, false, empty // singleton under pivot - elif c = 0 then empty, true, empty // singleton is pivot - else empty, false, t // singleton over pivot let rec spliceOutSuccessor (t:SetTree<'T>) = if isEmpty t then failwith "internal error: Set.spliceOutSuccessor" else - match t with - | :? SetTreeNode<'T> as tn -> + if t.Height = 1 then t.Key, empty + else + let tn = asNode t if isEmpty tn.Left then tn.Key, tn.Right else let k3, l' = spliceOutSuccessor tn.Left in k3, mk l' tn.Key tn.Right - | _ -> t.Key, empty let rec remove (comparer: IComparer<'T>) k (t:SetTree<'T>) = if isEmpty t then t else let c = comparer.Compare(k, t.Key) - match t with - | :? SetTreeNode<'T> as tn -> + if t.Height = 1 then + if c = 0 then empty else t + else + let tn = asNode t if c < 0 then rebalance (remove comparer k tn.Left) tn.Key tn.Right elif c = 0 then if isEmpty tn.Left then tn.Right @@ -229,63 +230,64 @@ module internal SetTree = else let sk, r' = spliceOutSuccessor tn.Right mk tn.Left sk r' - else rebalance tn.Left tn.Key (remove comparer k tn.Right) - | _ -> - if c = 0 then empty - else t + else rebalance tn.Left tn.Key (remove comparer k tn.Right) let rec mem (comparer: IComparer<'T>) k (t:SetTree<'T>) = if isEmpty t then false else let c = comparer.Compare(k, t.Key) - match t with - | :? SetTreeNode<'T> as tn -> + if t.Height = 1 then (c = 0) + else + let tn = asNode t if c < 0 then mem comparer k tn.Left elif c = 0 then true else mem comparer k tn.Right - | _ -> (c = 0) let rec iter f (t:SetTree<'T>) = if isEmpty t then () else - match t with - | :? SetTreeNode<'T> as tn -> iter f tn.Left; f tn.Key; iter f tn.Right - | _ -> f t.Key + if t.Height = 1 then f t.Key + else + let tn = asNode t + iter f tn.Left; f tn.Key; iter f tn.Right let rec foldBackOpt (f:OptimizedClosures.FSharpFunc<_, _, _>) (t:SetTree<'T>) x = if isEmpty t then x else - match t with - | :? SetTreeNode<'T> as tn -> foldBackOpt f tn.Left (f.Invoke(tn.Key, (foldBackOpt f tn.Right x))) - | _ -> f.Invoke(t.Key, x) + if t.Height = 1 then f.Invoke(t.Key, x) + else + let tn = asNode t + foldBackOpt f tn.Left (f.Invoke(tn.Key, (foldBackOpt f tn.Right x))) let foldBack f m x = foldBackOpt (OptimizedClosures.FSharpFunc<_, _, _>.Adapt f) m x let rec foldOpt (f:OptimizedClosures.FSharpFunc<_, _, _>) x (t:SetTree<'T>) = if isEmpty t then x else - match t with - | :? SetTreeNode<'T> as tn -> + if t.Height = 1 then f.Invoke(x, t.Key) + else + let tn = asNode t let x = foldOpt f x tn.Left in let x = f.Invoke(x, tn.Key) foldOpt f x tn.Right - | _ -> f.Invoke(x, t.Key) let fold f x m = foldOpt (OptimizedClosures.FSharpFunc<_, _, _>.Adapt f) x m let rec forall f (t:SetTree<'T>) = if isEmpty t then true else - match t with - | :? SetTreeNode<'T> as tn -> f tn.Key && forall f tn.Left && forall f tn.Right - | _ -> f t.Key + if t.Height = 1 then f t.Key + else + let tn = asNode t + f tn.Key && forall f tn.Left && forall f tn.Right let rec exists f (t:SetTree<'T>) = if isEmpty t then false else - match t with - | :? SetTreeNode<'T> as tn -> f tn.Key || exists f tn.Left || exists f tn.Right - | _ -> f t.Key + if t.Height = 1 then f t.Key + else + let tn = asNode t + f tn.Key || exists f tn.Left || exists f tn.Right let subset comparer a b = forall (fun x -> mem comparer x b) a @@ -296,11 +298,12 @@ module internal SetTree = let rec filterAux comparer f (t:SetTree<'T>) acc = if isEmpty t then acc else - match t with - | :? SetTreeNode<'T> as tn -> + if t.Height = 1 then + if f t.Key then add comparer t.Key acc else acc + else + let tn = asNode t let acc = if f tn.Key then add comparer tn.Key acc else acc filterAux comparer f tn.Left (filterAux comparer f tn.Right acc) - | _ -> if f t.Key then add comparer t.Key acc else acc let filter comparer f s = filterAux comparer f s empty @@ -309,9 +312,10 @@ module internal SetTree = else if isEmpty t then acc else - match t with - | :? SetTreeNode<'T> as tn -> diffAux comparer tn.Left (diffAux comparer tn.Right (remove comparer tn.Key acc)) - | _ -> remove comparer t.Key acc + if t.Height = 1 then remove comparer t.Key acc + else + let tn = asNode t + diffAux comparer tn.Left (diffAux comparer tn.Right (remove comparer tn.Key acc)) let diff comparer a b = diffAux comparer b a @@ -320,10 +324,12 @@ module internal SetTree = if isEmpty t1 then t2 elif isEmpty t2 then t1 else - match t1 with - | :? SetTreeNode<'T> as t1n -> - match t2 with - | :? SetTreeNode<'T> as t2n -> // (t1l < k < t1r) AND (t2l < k2 < t2r) + if t1.Height = 1 then add comparer t1.Key t2 + else + let t1n = asNode t1 + if t2.Height = 1 then add comparer t2.Key t1 + else + let t2n = asNode t2 // (t1l < k < t1r) AND (t2l < k2 < t2r) // Divide and Conquer: // Suppose t1 is largest. // Split t2 using pivot k1 into lo and hi. @@ -334,19 +340,17 @@ module internal SetTree = else let lo, _, hi = split comparer t2n.Key t1 in balance comparer (union comparer t2n.Left lo) t2n.Key (union comparer t2n.Right hi) - | _ -> add comparer t2.Key t1 - | _ -> add comparer t1.Key t2 let rec intersectionAux comparer b (t:SetTree<'T>) acc = if isEmpty t then acc else - match t with - | :? SetTreeNode<'T> as tn -> + if t.Height = 1 then + if mem comparer t.Key b then add comparer t.Key acc else acc + else + let tn = asNode t let acc = intersectionAux comparer b tn.Right acc let acc = if mem comparer tn.Key b then add comparer tn.Key acc else acc intersectionAux comparer b tn.Left acc - | _ -> - if mem comparer t.Key b then add comparer t.Key acc else acc let intersection comparer a b = intersectionAux comparer b a empty @@ -355,42 +359,46 @@ module internal SetTree = let rec partitionAux comparer f (t:SetTree<'T>) acc = if isEmpty t then acc else - match t with - | :? SetTreeNode<'T> as tn -> + if t.Height = 1 then partition1 comparer f t.Key acc + else + let tn = asNode t let acc = partitionAux comparer f tn.Right acc let acc = partition1 comparer f tn.Key acc partitionAux comparer f tn.Left acc - | _ -> partition1 comparer f t.Key acc let partition comparer f s = partitionAux comparer f s (empty, empty) let rec minimumElementAux (t:SetTree<'T>) n = if isEmpty t then n else - match t with - | :? SetTreeNode<'T> as tn -> minimumElementAux tn.Left tn.Key - | _ -> t.Key + if t.Height = 1 then t.Key + else + let tn = asNode t + minimumElementAux tn.Left tn.Key and minimumElementOpt (t:SetTree<'T>) = if isEmpty t then None else - match t with - | :? SetTreeNode<'T> as tn -> Some(minimumElementAux tn.Left tn.Key) - | _ -> Some t.Key + if t.Height = 1 then Some t.Key + else + let tn = asNode t + Some(minimumElementAux tn.Left tn.Key) and maximumElementAux (t:SetTree<'T>) n = if isEmpty t then n else - match t with - | :? SetTreeNode<'T> as tn -> maximumElementAux tn.Right tn.Key - | _ -> t.Key + if t.Height = 1 then t.Key + else + let tn = asNode t + maximumElementAux tn.Right tn.Key and maximumElementOpt (t:SetTree<'T>) = if isEmpty t then None else - match t with - | :? SetTreeNode<'T> as tn -> Some(maximumElementAux tn.Right tn.Key) - | _ -> Some t.Key + if t.Height = 1 then Some t.Key + else + let tn = asNode t + Some(maximumElementAux tn.Right tn.Key) let minimumElement s = match minimumElementOpt s with @@ -418,9 +426,10 @@ module internal SetTree = | x :: rest -> if isEmpty x then collapseLHS rest else - match x with - | :? SetTreeNode<'T> as xn-> collapseLHS (xn.Left :: SetTree xn.Key :: xn.Right :: rest) - | _ -> stack + if x.Height = 1 then stack + else + let xn = asNode x + collapseLHS (xn.Left :: SetTree xn.Key :: xn.Right :: rest) let mkIterator s = { stack = collapseLHS [s]; started = false } @@ -436,16 +445,19 @@ module internal SetTree = else notStarted() + let unexpectedStackForMoveNext() = failwith "Please report error: Set iterator, unexpected stack for moveNext" + let unexpectedstateInSetTreeCompareStacks() = failwith "unexpected state in SetTree.compareStacks" + let rec moveNext i = if i.started then match i.stack with | [] -> false | t :: rest -> - match t with - | :? SetTreeNode<'T> -> failwith "Please report error: Set iterator, unexpected stack for moveNext" - | _ -> + if t.Height = 1 then i.stack <- collapseLHS rest - not i.stack.IsEmpty + not i.stack.IsEmpty + else + unexpectedStackForMoveNext() else i.started <- true; // The first call to MoveNext "starts" the enumeration. not i.stack.IsEmpty @@ -466,16 +478,18 @@ module internal SetTree = let cont() = match l1, l2 with | (x1 :: t1), _ when not (isEmpty x1) -> - match x1 with - | :? SetTreeNode<'T> as x1n -> + if x1.Height = 1 then + compareStacks comparer (empty :: SetTree x1.Key :: t1) l2 + else + let x1n = asNode x1 compareStacks comparer (x1n.Left :: (SetTreeNode (x1n.Key, empty, x1n.Right, 0) :> SetTree<'T>) :: t1) l2 - | _ -> compareStacks comparer (empty :: SetTree x1.Key :: t1) l2 | _, (x2 :: t2) when not (isEmpty x2) -> - match x2 with - | :? SetTreeNode<'T> as x2n -> + if x2.Height = 1 then + compareStacks comparer l1 (empty :: SetTree x2.Key :: t2) + else + let x2n = asNode x2 compareStacks comparer l1 (x2n.Left :: (SetTreeNode (x2n.Key, empty, x2n.Right, 0) :> SetTree<'T> ) :: t2) - | _ -> compareStacks comparer l1 (empty :: SetTree x2.Key :: t2) - | _ -> failwith "unexpected state in SetTree.compareStacks" + | _ -> unexpectedstateInSetTreeCompareStacks() match l1, l2 with | [], [] -> 0 @@ -487,30 +501,30 @@ module internal SetTree = else cont() elif isEmpty x2 then cont() else - match x1 with - | :? SetTreeNode<'T> as x1n -> + if x1.Height = 1 then + if x2.Height = 1 then + let c = comparer.Compare(x1.Key, x2.Key) + if c <> 0 then c else compareStacks comparer t1 t2 + else + let x2n = asNode x2 + if isEmpty x2n.Left then + let c = comparer.Compare(x1.Key, x2n.Key) + if c <> 0 then c else compareStacks comparer (empty :: t1) (x2n.Right :: t2) + else cont() + else + let x1n = asNode x1 if isEmpty x1n.Left then - match x2 with - | :? SetTreeNode<'T> as x2n -> + if x2.Height = 1 then + let c = comparer.Compare(x1n.Key, x2.Key) + if c <> 0 then c else compareStacks comparer (x1n.Right :: t1) (empty :: t2) + else + let x2n = asNode x2 if isEmpty x2n.Left then let c = comparer.Compare(x1n.Key, x2n.Key) if c <> 0 then c else compareStacks comparer (x1n.Right :: t1) (x2n.Right :: t2) else cont() - | _ -> - let c = comparer.Compare(x1n.Key, x2.Key) - if c <> 0 then c else compareStacks comparer (x1n.Right :: t1) (empty :: t2) else cont() - | _ -> - match x2 with - | :? SetTreeNode<'T> as x2n -> - if isEmpty x2n.Left then - let c = comparer.Compare(x1.Key, x2n.Key) - if c <> 0 then c else compareStacks comparer (empty :: t1) (x2n.Right :: t2) - else cont() - | _ -> - let c = comparer.Compare(x1.Key, x2.Key) - if c <> 0 then c else compareStacks comparer t1 t2 - + let compare comparer (t1:SetTree<'T>) (t2:SetTree<'T>) = if isEmpty t1 then if isEmpty t2 then 0 @@ -526,9 +540,10 @@ module internal SetTree = let rec loop (t':SetTree<'T>) acc = if isEmpty t' then acc else - match t' with - | :? SetTreeNode<'T> as tn -> loop tn.Left (tn.Key :: loop tn.Right acc) - | _ -> t'.Key :: acc + if t'.Height = 1 then t'.Key :: acc + else + let tn = asNode t + loop tn.Left (tn.Key :: loop tn.Right acc) loop t [] let copyToArray s (arr: _[]) i = From 79a8e7fb35cd35adc54d6547361ef1a569a52fb0 Mon Sep 17 00:00:00 2001 From: Victor Baybekov Date: Sun, 10 Jan 2021 02:35:34 +0100 Subject: [PATCH 5/6] Set: Optimize away `isinst` check: remove duplicate compare call in add --- src/fsharp/FSharp.Core/set.fs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/fsharp/FSharp.Core/set.fs b/src/fsharp/FSharp.Core/set.fs index f09ca1528db..33720d7066a 100644 --- a/src/fsharp/FSharp.Core/set.fs +++ b/src/fsharp/FSharp.Core/set.fs @@ -145,7 +145,6 @@ module internal SetTree = let c = comparer.Compare(k, t.Key) if t.Height = 1 then // nb. no check for rebalance needed for small trees, also be sure to reuse node already allocated - let c = comparer.Compare(k, t.Key) if c < 0 then SetTreeNode (k, empty, t, 2) :> SetTree<'T> elif c = 0 then t else SetTreeNode (k, t, empty, 2) :> SetTree<'T> From 1475159796d695759ff390563dafd5c94c4407fc Mon Sep 17 00:00:00 2001 From: Victor Baybekov Date: Sun, 10 Jan 2021 03:49:21 +0100 Subject: [PATCH 6/6] Set: Optimize away `isinst` check: fix stack overflow --- src/fsharp/FSharp.Core/set.fs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/fsharp/FSharp.Core/set.fs b/src/fsharp/FSharp.Core/set.fs index 33720d7066a..29f6af81673 100644 --- a/src/fsharp/FSharp.Core/set.fs +++ b/src/fsharp/FSharp.Core/set.fs @@ -325,9 +325,9 @@ module internal SetTree = else if t1.Height = 1 then add comparer t1.Key t2 else - let t1n = asNode t1 if t2.Height = 1 then add comparer t2.Key t1 else + let t1n = asNode t1 let t2n = asNode t2 // (t1l < k < t1r) AND (t2l < k2 < t2r) // Divide and Conquer: // Suppose t1 is largest. @@ -541,7 +541,7 @@ module internal SetTree = else if t'.Height = 1 then t'.Key :: acc else - let tn = asNode t + let tn = asNode t' loop tn.Left (tn.Key :: loop tn.Right acc) loop t []