diff --git a/src/fsharp/FSharp.Core/array.fs b/src/fsharp/FSharp.Core/array.fs index 77654b56bcb..4e616a8b3c6 100644 --- a/src/fsharp/FSharp.Core/array.fs +++ b/src/fsharp/FSharp.Core/array.fs @@ -6,6 +6,7 @@ namespace Microsoft.FSharp.Collections open System.Diagnostics open System.Collections.Generic open System.Diagnostics.CodeAnalysis + open System.Reflection open Microsoft.FSharp.Core open Microsoft.FSharp.Core.CompilerServices open Microsoft.FSharp.Collections @@ -14,8 +15,7 @@ namespace Microsoft.FSharp.Collections open Microsoft.FSharp.Core.SR #if FX_NO_ICLONEABLE open Microsoft.FSharp.Core.ICloneableExtensions -#else -#endif +#endif /// Basic operations on arrays [] @@ -172,24 +172,39 @@ namespace Microsoft.FSharp.Collections Microsoft.FSharp.Primitives.Basics.Array.subUnchecked 0 count array - [] - let countBy projection (array:'T[]) = - checkNonNull "array" array - let dict = new Dictionary,int>(Microsoft.FSharp.Core.CompilerServices.RuntimeHelpers.StructBox<'Key>.Comparer) + let inline countByImpl (comparer:IEqualityComparer<'SafeKey>) (projection:'T->'SafeKey) (getKey:'SafeKey->'Key) (array:'T[]) = + let dict = Dictionary comparer // Build the groupings for v in array do - let key = Microsoft.FSharp.Core.CompilerServices.RuntimeHelpers.StructBox (projection v) + let safeKey = projection v let mutable prev = Unchecked.defaultof<_> - if dict.TryGetValue(key, &prev) then dict.[key] <- prev + 1 else dict.[key] <- 1 + if dict.TryGetValue(safeKey, &prev) then dict.[safeKey] <- prev + 1 else dict.[safeKey] <- 1 let res = Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked dict.Count let mutable i = 0 for group in dict do - res.[i] <- group.Key.Value, group.Value + res.[i] <- getKey group.Key, group.Value i <- i + 1 res + // We avoid wrapping a StructBox, because under 64 JIT we get some "hard" tailcalls which affect performance + let countByValueType (projection:'T->'Key) (array:'T[]) = countByImpl HashIdentity.Structural<'Key> projection id array + + // Wrap a StructBox around all keys in case the key type is itself a type using null as a representation + let countByRefType (projection:'T->'Key) (array:'T[]) = countByImpl Microsoft.FSharp.Core.CompilerServices.RuntimeHelpers.StructBox<'Key>.Comparer (fun t -> Microsoft.FSharp.Core.CompilerServices.RuntimeHelpers.StructBox (projection t)) (fun sb -> sb.Value) array + + [] + let countBy (projection:'T->'Key) (array:'T[]) = + checkNonNull "array" array +#if FX_RESHAPED_REFLECTION + if (typeof<'Key>).GetTypeInfo().IsValueType +#else + if typeof<'Key>.IsValueType +#endif + then countByValueType projection array + else countByRefType projection array + [] let append (array1:'T[]) (array2:'T[]) = checkNonNull "array1" array1 @@ -408,32 +423,47 @@ namespace Microsoft.FSharp.Collections let rec loop i = i >= len1 || (f.Invoke(array1.[i], array2.[i]) && loop (i+1)) loop 0 - [] - let groupBy keyf (array: 'T[]) = - checkNonNull "array" array - let dict = new Dictionary,ResizeArray<'T>>(RuntimeHelpers.StructBox<'Key>.Comparer) + let inline groupByImpl (comparer:IEqualityComparer<'SafeKey>) (keyf:'T->'SafeKey) (getKey:'SafeKey->'Key) (array: 'T[]) = + let dict = Dictionary<_,ResizeArray<_>> comparer // Build the groupings for i = 0 to (array.Length - 1) do let v = array.[i] - let key = RuntimeHelpers.StructBox (keyf v) - let ok, prev = dict.TryGetValue(key) - if ok then - prev.Add(v) + let safeKey = keyf v + let mutable prev = Unchecked.defaultof<_> + if dict.TryGetValue(safeKey, &prev) then + prev.Add v else - let prev = new ResizeArray<'T>(1) - dict.[key] <- prev - prev.Add(v) + let prev = ResizeArray () + dict.[safeKey] <- prev + prev.Add v // Return the array-of-arrays. let result = Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked dict.Count let mutable i = 0 for group in dict do - result.[i] <- group.Key.Value, group.Value.ToArray() + result.[i] <- getKey group.Key, group.Value.ToArray() i <- i + 1 result + // We avoid wrapping a StructBox, because under 64 JIT we get some "hard" tailcalls which affect performance + let groupByValueType (keyf:'T->'Key) (array:'T[]) = groupByImpl HashIdentity.Structural<'Key> keyf id array + + // Wrap a StructBox around all keys in case the key type is itself a type using null as a representation + let groupByRefType (keyf:'T->'Key) (array:'T[]) = groupByImpl Microsoft.FSharp.Core.CompilerServices.RuntimeHelpers.StructBox<'Key>.Comparer (fun t -> Microsoft.FSharp.Core.CompilerServices.RuntimeHelpers.StructBox (keyf t)) (fun sb -> sb.Value) array + + [] + let groupBy (keyf:'T->'Key) (array:'T[]) = + checkNonNull "array" array +#if FX_RESHAPED_REFLECTION + if (typeof<'Key>).GetTypeInfo().IsValueType +#else + if typeof<'Key>.IsValueType +#endif + then groupByValueType keyf array + else groupByRefType keyf array + [] let pick f (array: _[]) = checkNonNull "array" array diff --git a/src/fsharp/FSharp.Core/fslib-extra-pervasives.fs b/src/fsharp/FSharp.Core/fslib-extra-pervasives.fs index 0fa7edf74e2..087b43d0c4a 100644 --- a/src/fsharp/FSharp.Core/fslib-extra-pervasives.fs +++ b/src/fsharp/FSharp.Core/fslib-extra-pervasives.fs @@ -8,6 +8,7 @@ module ExtraTopLevelOperators = open System.Collections.Generic open System.IO open System.Diagnostics + open System.Reflection open Microsoft.FSharp open Microsoft.FSharp.Core open Microsoft.FSharp.Core.Operators @@ -30,19 +31,16 @@ module ExtraTopLevelOperators = [] let set l = Collections.Set.ofSeq l - [] - let dict l = - // Use a dictionary (this requires hashing and equality on the key type) - // Wrap keys in a StructBox in case they are null (when System.Collections.Generic.Dictionary fails). - let t = new Dictionary,_>(RuntimeHelpers.StructBox<'Key>.Comparer) + let inline dictImpl (comparer:IEqualityComparer<'SafeKey>) (makeSafeKey:'Key->'SafeKey) (getKey:'SafeKey->'Key) (l:seq<'Key*'T>) = + let t = Dictionary comparer for (k,v) in l do - t.[RuntimeHelpers.StructBox(k)] <- v + t.[makeSafeKey k] <- v let d = (t :> IDictionary<_,_>) let c = (t :> ICollection<_>) // Give a read-only view of the dictionary { new IDictionary<'Key, 'T> with member s.Item - with get x = d.[RuntimeHelpers.StructBox(x)] + with get x = d.[makeSafeKey x] and set x v = raise (NotSupportedException(SR.GetString(SR.thisValueCannotBeMutated))) member s.Keys = let keys = d.Keys @@ -50,45 +48,60 @@ module ExtraTopLevelOperators = member s.Add(x) = raise (NotSupportedException(SR.GetString(SR.thisValueCannotBeMutated))); member s.Clear() = raise (NotSupportedException(SR.GetString(SR.thisValueCannotBeMutated))); member s.Remove(x) = raise (NotSupportedException(SR.GetString(SR.thisValueCannotBeMutated))); - member s.Contains(x) = keys.Contains(RuntimeHelpers.StructBox(x)) + member s.Contains(x) = keys.Contains(makeSafeKey x) member s.CopyTo(arr,i) = let mutable n = 0 for k in keys do - arr.[i+n] <- k.Value + arr.[i+n] <- getKey k n <- n + 1 member s.IsReadOnly = true member s.Count = keys.Count interface IEnumerable<'Key> with - member s.GetEnumerator() = (keys |> Seq.map (fun v -> v.Value)).GetEnumerator() + member s.GetEnumerator() = (keys |> Seq.map getKey).GetEnumerator() interface System.Collections.IEnumerable with - member s.GetEnumerator() = ((keys |> Seq.map (fun v -> v.Value)) :> System.Collections.IEnumerable).GetEnumerator() } + member s.GetEnumerator() = ((keys |> Seq.map getKey) :> System.Collections.IEnumerable).GetEnumerator() } member s.Values = d.Values member s.Add(k,v) = raise (NotSupportedException(SR.GetString(SR.thisValueCannotBeMutated))) - member s.ContainsKey(k) = d.ContainsKey(RuntimeHelpers.StructBox(k)) + member s.ContainsKey(k) = d.ContainsKey(makeSafeKey k) member s.TryGetValue(k,r) = - let key = RuntimeHelpers.StructBox(k) - if d.ContainsKey(key) then (r <- d.[key]; true) else false + let safeKey = makeSafeKey k + if d.ContainsKey(safeKey) then (r <- d.[safeKey]; true) else false member s.Remove(k : 'Key) = (raise (NotSupportedException(SR.GetString(SR.thisValueCannotBeMutated))) : bool) interface ICollection> with member s.Add(x) = raise (NotSupportedException(SR.GetString(SR.thisValueCannotBeMutated))); member s.Clear() = raise (NotSupportedException(SR.GetString(SR.thisValueCannotBeMutated))); member s.Remove(x) = raise (NotSupportedException(SR.GetString(SR.thisValueCannotBeMutated))); - member s.Contains(KeyValue(k,v)) = c.Contains(KeyValuePair<_,_>(RuntimeHelpers.StructBox(k),v)) + member s.Contains(KeyValue(k,v)) = c.Contains(KeyValuePair<_,_>(makeSafeKey k,v)) member s.CopyTo(arr,i) = let mutable n = 0 for (KeyValue(k,v)) in c do - arr.[i+n] <- KeyValuePair<_,_>(k.Value,v) + arr.[i+n] <- KeyValuePair<_,_>(getKey k,v) n <- n + 1 member s.IsReadOnly = true member s.Count = c.Count interface IEnumerable> with member s.GetEnumerator() = - (c |> Seq.map (fun (KeyValue(k,v)) -> KeyValuePair<_,_>(k.Value,v))).GetEnumerator() + (c |> Seq.map (fun (KeyValue(k,v)) -> KeyValuePair<_,_>(getKey k,v))).GetEnumerator() interface System.Collections.IEnumerable with member s.GetEnumerator() = - ((c |> Seq.map (fun (KeyValue(k,v)) -> KeyValuePair<_,_>(k.Value,v))) :> System.Collections.IEnumerable).GetEnumerator() } + ((c |> Seq.map (fun (KeyValue(k,v)) -> KeyValuePair<_,_>(getKey k,v))) :> System.Collections.IEnumerable).GetEnumerator() } + + // We avoid wrapping a StructBox, because under 64 JIT we get some "hard" tailcalls which affect performance + let dictValueType (l:seq<'Key*'T>) = dictImpl HashIdentity.Structural<'Key> id id l + // Wrap a StructBox around all keys in case the key type is itself a type using null as a representation + let dictRefType (l:seq<'Key*'T>) = dictImpl RuntimeHelpers.StructBox<'Key>.Comparer (fun k -> RuntimeHelpers.StructBox k) (fun sb -> sb.Value) l + + [] + let dict (l:seq<'Key*'T>) = +#if FX_RESHAPED_REFLECTION + if (typeof<'Key>).GetTypeInfo().IsValueType +#else + if typeof<'Key>.IsValueType +#endif + then dictValueType l + else dictRefType l let getArray (vals : seq<'T>) = match vals with diff --git a/src/fsharp/FSharp.Core/list.fs b/src/fsharp/FSharp.Core/list.fs index 541e5e31fc3..4e87a88fd8c 100644 --- a/src/fsharp/FSharp.Core/list.fs +++ b/src/fsharp/FSharp.Core/list.fs @@ -3,6 +3,7 @@ namespace Microsoft.FSharp.Collections open System.Diagnostics + open System.Reflection open Microsoft.FSharp.Core open Microsoft.FSharp.Core.Operators open Microsoft.FSharp.Core.LanguagePrimitives @@ -41,23 +42,38 @@ namespace Microsoft.FSharp.Collections [] let concat lists = Microsoft.FSharp.Primitives.Basics.List.concat lists - [] - let countBy projection (list:'T list) = - let dict = new Dictionary,int>(Microsoft.FSharp.Core.CompilerServices.RuntimeHelpers.StructBox<'Key>.Comparer) + let inline countByImpl (comparer:IEqualityComparer<'SafeKey>) (projection:'T->'SafeKey) (getKey:'SafeKey->'Key) (list:'T list) = + let dict = Dictionary comparer let rec loop srcList = match srcList with | [] -> () | h::t -> - let key = Microsoft.FSharp.Core.CompilerServices.RuntimeHelpers.StructBox (projection h) + let safeKey = projection h let mutable prev = 0 - if dict.TryGetValue(key, &prev) then dict.[key] <- prev + 1 else dict.[key] <- 1 + if dict.TryGetValue(safeKey, &prev) then dict.[safeKey] <- prev + 1 else dict.[safeKey] <- 1 loop t loop list let mutable result = [] for group in dict do - result <- (group.Key.Value, group.Value) :: result + result <- (getKey group.Key, group.Value) :: result result |> rev + // We avoid wrapping a StructBox, because under 64 JIT we get some "hard" tailcalls which affect performance + let countByValueType (projection:'T->'Key) (list:'T list) = countByImpl HashIdentity.Structural<'Key> projection id list + + // Wrap a StructBox around all keys in case the key type is itself a type using null as a representation + let countByRefType (projection:'T->'Key) (list:'T list) = countByImpl Microsoft.FSharp.Core.CompilerServices.RuntimeHelpers.StructBox<'Key>.Comparer (fun t -> Microsoft.FSharp.Core.CompilerServices.RuntimeHelpers.StructBox (projection t)) (fun sb -> sb.Value) list + + [] + let countBy (projection:'T->'Key) (list:'T list) = +#if FX_RESHAPED_REFLECTION + if (typeof<'Key>).GetTypeInfo().IsValueType +#else + if typeof<'Key>.IsValueType +#endif + then countByValueType projection list + else countByRefType projection list + [] let map f list = Microsoft.FSharp.Primitives.Basics.List.map f list @@ -434,31 +450,46 @@ namespace Microsoft.FSharp.Collections [] let where f x = Microsoft.FSharp.Primitives.Basics.List.filter f x - [] - let groupBy keyf (list: 'T list) = - let dict = new Dictionary,ResizeArray<'T>>(Microsoft.FSharp.Core.CompilerServices.RuntimeHelpers.StructBox<'Key>.Comparer) + let inline groupByImpl (comparer:IEqualityComparer<'SafeKey>) (keyf:'T->'SafeKey) (getKey:'SafeKey->'Key) (list: 'T list) = + let dict = Dictionary<_,ResizeArray<_>> comparer // Build the groupings let rec loop list = match list with | v :: t -> - let key = Microsoft.FSharp.Core.CompilerServices.RuntimeHelpers.StructBox (keyf v) - let ok,prev = dict.TryGetValue(key) - if ok then - prev.Add(v) + let safeKey = keyf v + let mutable prev = Unchecked.defaultof<_> + if dict.TryGetValue(safeKey, &prev) then + prev.Add v else - let prev = new ResizeArray<'T>(1) - dict.[key] <- prev - prev.Add(v) + let prev = ResizeArray () + dict.[safeKey] <- prev + prev.Add v loop t | _ -> () loop list // Return the list-of-lists. dict - |> Seq.map (fun group -> (group.Key.Value, Seq.toList group.Value)) + |> Seq.map (fun group -> (getKey group.Key, Seq.toList group.Value)) |> Seq.toList + // We avoid wrapping a StructBox, because under 64 JIT we get some "hard" tailcalls which affect performance + let groupByValueType (keyf:'T->'Key) (list:'T list) = groupByImpl HashIdentity.Structural<'Key> keyf id list + + // Wrap a StructBox around all keys in case the key type is itself a type using null as a representation + let groupByRefType (keyf:'T->'Key) (list:'T list) = groupByImpl Microsoft.FSharp.Core.CompilerServices.RuntimeHelpers.StructBox<'Key>.Comparer (fun t -> Microsoft.FSharp.Core.CompilerServices.RuntimeHelpers.StructBox (keyf t)) (fun sb -> sb.Value) list + + [] + let groupBy (keyf:'T->'Key) (list:'T list) = +#if FX_RESHAPED_REFLECTION + if (typeof<'Key>).GetTypeInfo().IsValueType +#else + if typeof<'Key>.IsValueType +#endif + then groupByValueType keyf list + else groupByRefType keyf list + [] let partition p x = Microsoft.FSharp.Primitives.Basics.List.partition p x diff --git a/src/fsharp/FSharp.Core/seq.fs b/src/fsharp/FSharp.Core/seq.fs index fc297daebc2..aa1b70cbbc9 100644 --- a/src/fsharp/FSharp.Core/seq.fs +++ b/src/fsharp/FSharp.Core/seq.fs @@ -847,6 +847,7 @@ namespace Microsoft.FSharp.Collections open System.Diagnostics open System.Collections open System.Collections.Generic + open System.Reflection open Microsoft.FSharp.Core open Microsoft.FSharp.Core.LanguagePrimitives.IntrinsicOperators open Microsoft.FSharp.Core.Operators @@ -1444,32 +1445,50 @@ namespace Microsoft.FSharp.Collections checkNonNull "source" source mkSeq (fun () -> source.GetEnumerator()) + let inline groupByImpl (comparer:IEqualityComparer<'SafeKey>) (keyf:'T->'SafeKey) (getKey:'SafeKey->'Key) (seq:seq<'T>) = + checkNonNull "seq" seq + + let dict = Dictionary<_,ResizeArray<_>> comparer + + // Previously this was 1, but I think this is rather stingy, considering that we are alreadying paying + // for at least a key, the ResizeArray reference, which includes an array reference, an Entry in the + // Dictionary, plus any empty space in the Dictionary of unfilled hash buckets. + let minimumBucketSize = 4 + + // Build the groupings + seq |> iter (fun v -> + let safeKey = keyf v + let mutable prev = Unchecked.defaultof<_> + match dict.TryGetValue (safeKey, &prev) with + | true -> prev.Add v + | false -> + let prev = ResizeArray () + dict.[safeKey] <- prev + prev.Add v) + + // Trim the size of each result group, don't trim very small buckets, as excessive work, and garbage for + // minimal gain + dict |> iter (fun group -> if group.Value.Count > minimumBucketSize then group.Value.TrimExcess()) + + // Return the sequence-of-sequences. Don't reveal the + // internal collections: just reveal them as sequences + dict |> map (fun group -> (getKey group.Key, readonly group.Value)) + // We avoid wrapping a StructBox, because under 64 JIT we get some "hard" tailcalls which affect performance + let groupByValueType (keyf:'T->'Key) (seq:seq<'T>) = seq |> groupByImpl HashIdentity.Structural<'Key> keyf id - [] - let groupBy keyf seq = - - mkDelayedSeq (fun () -> - // Wrap a StructBox(_) around all keys in case the key type is itself a type using null as a representation - let dict = new Dictionary,ResizeArray<'T>>(StructBox<'Key>.Comparer) - - // Build the groupings - seq |> iter (fun v -> - let key = StructBox (keyf v) - let ok,prev = dict.TryGetValue(key) - if ok then - prev.Add(v) - else - let prev = new ResizeArray<'T>(1) - dict.[key] <- prev - prev.Add(v)) + // Wrap a StructBox around all keys in case the key type is itself a type using null as a representation + let groupByRefType (keyf:'T->'Key) (seq:seq<'T>) = seq |> groupByImpl StructBox<'Key>.Comparer (fun t -> StructBox (keyf t)) (fun sb -> sb.Value) - // Trim the size of each result group. - dict |> iter (fun group -> group.Value.TrimExcess()) - - // Return the sequence-of-sequences. Don't reveal the - // internal collections: just reveal them as sequences - dict |> map (fun group -> (group.Key.Value, readonly group.Value))) + [] + let groupBy (keyf:'T->'Key) (seq:seq<'T>) = +#if FX_RESHAPED_REFLECTION + if (typeof<'Key>).GetTypeInfo().IsValueType +#else + if typeof<'Key>.IsValueType +#endif + then mkDelayedSeq (fun () -> groupByValueType keyf seq) + else mkDelayedSeq (fun () -> groupByRefType keyf seq) [] let distinct source = @@ -1523,19 +1542,38 @@ namespace Microsoft.FSharp.Collections let inline compareDescending a b = compare b a sortWith compareDescending source - [] - let countBy keyf source = + let inline countByImpl (comparer:IEqualityComparer<'SafeKey>) (keyf:'T->'SafeKey) (getKey:'SafeKey->'Key) (source:seq<'T>) = checkNonNull "source" source - mkDelayedSeq (fun () -> - let dict = new Dictionary,int>(StructBox<'Key>.Comparer) - // Build the groupings - source |> iter (fun v -> - let key = StructBox (keyf v ) - let mutable prev = Unchecked.defaultof<_> - if dict.TryGetValue(key, &prev) then dict.[key] <- prev + 1 else dict.[key] <- 1) + let dict = Dictionary comparer + + // Build the groupings + source |> iter (fun v -> + let safeKey = keyf v + let mutable prev = Unchecked.defaultof<_> + if dict.TryGetValue(safeKey, &prev) + then dict.[safeKey] <- prev + 1 + else dict.[safeKey] <- 1) + + dict |> map (fun group -> (getKey group.Key, group.Value)) + + // We avoid wrapping a StructBox, because under 64 JIT we get some "hard" tailcalls which affect performance + let countByValueType (keyf:'T->'Key) (seq:seq<'T>) = seq |> countByImpl HashIdentity.Structural<'Key> keyf id + + // Wrap a StructBox around all keys in case the key type is itself a type using null as a representation + let countByRefType (keyf:'T->'Key) (seq:seq<'T>) = seq |> countByImpl StructBox<'Key>.Comparer (fun t -> StructBox (keyf t)) (fun sb -> sb.Value) - dict |> map (fun group -> (group.Key.Value, group.Value))) + [] + let countBy (keyf:'T->'Key) (source:seq<'T>) = + checkNonNull "source" source + +#if FX_RESHAPED_REFLECTION + if (typeof<'Key>).GetTypeInfo().IsValueType +#else + if typeof<'Key>.IsValueType +#endif + then mkDelayedSeq (fun () -> countByValueType keyf source) + else mkDelayedSeq (fun () -> countByRefType keyf source) [] let inline sum (source: seq< (^a) >) : ^a =