@@ -1444,32 +1444,45 @@ namespace Microsoft.FSharp.Collections
14441444 checkNonNull " source" source
14451445 mkSeq ( fun () -> source.GetEnumerator())
14461446
1447-
1448-
14491447 [<CompiledName( " GroupBy" ) >]
1450- let groupBy keyf seq =
1448+ let inline groupByImpl ( comparer : IEqualityComparer < 'SafeKey >) ( keyf : 'T -> 'SafeKey ) ( getKey : 'SafeKey -> 'Key ) ( seq : seq < 'T >) =
1449+ let dict = Dictionary<_, ResizeArray<_>> comparer
1450+
1451+ // Previously this was 1, but I think this is rather stingy, considering that we are alreadying paying
1452+ // for at least a key, the ResizeArray reference, which includes an array reference, an Entry in the
1453+ // Dictionary, plus any empty space in the Dictionary of unfilled hash buckets.
1454+ let minimumBucketSize = 4
1455+
1456+ // Build the groupings
1457+ seq |> iter ( fun v ->
1458+ let key = keyf v
1459+ let mutable prev = Unchecked.defaultof<_>
1460+ match dict.TryGetValue ( key, & prev) with
1461+ | true -> prev.Add v
1462+ | false ->
1463+ let prev = ResizeArray minimumBucketSize
1464+ dict.[ key] <- prev
1465+ prev.Add v)
1466+
1467+ // Trim the size of each result group, don't trim very small buckets, as excessive work, and garbage for
1468+ // minimal gain
1469+ dict |> iter ( fun group -> if group.Value.Count > minimumBucketSize then group.Value.TrimExcess())
1470+
1471+ // Return the sequence-of-sequences. Don't reveal the
1472+ // internal collections: just reveal them as sequences
1473+ dict |> map ( fun group -> ( getKey group.Key, readonly group.Value))
14511474
1452- mkDelayedSeq ( fun () ->
1453- // Wrap a StructBox(_) around all keys in case the key type is itself a type using null as a representation
1454- let dict = new Dictionary< StructBox< 'Key>, ResizeArray< 'T>>( StructBox< 'Key>. Comparer)
1475+ // We avoid wrapping a StructBox, because under 64 JIT we get some "hard" tailcalls which affect performance
1476+ let groupByValueType ( keyf : 'T -> 'Key ) ( seq : seq < 'T >) = seq |> groupByImpl HashIdentity.Structural< 'Key> keyf id
14551477
1456- // Build the groupings
1457- seq |> iter ( fun v ->
1458- let key = StructBox ( keyf v)
1459- let ok , prev = dict.TryGetValue( key)
1460- if ok then
1461- prev.Add( v)
1462- else
1463- let prev = new ResizeArray< 'T>( 1 )
1464- dict.[ key] <- prev
1465- prev.Add( v))
1478+ // Wrap a StructBox around all keys in case the key type is itself a type using null as a representation
1479+ let groupByRefType ( keyf : 'T -> 'Key ) ( seq : seq < 'T >) = seq |> groupByImpl StructBox< 'Key>. Comparer ( fun t -> StructBox ( keyf t)) ( fun sb -> sb.Value)
14661480
1467- // Trim the size of each result group.
1468- dict |> iter ( fun group -> group.Value.TrimExcess())
1469-
1470- // Return the sequence-of-sequences. Don't reveal the
1471- // internal collections: just reveal them as sequences
1472- dict |> map ( fun group -> ( group.Key.Value, readonly group.Value)))
1481+ [<CompiledName( " GroupBy" ) >]
1482+ let groupBy ( keyf : 'T -> 'Key ) ( seq : seq < 'T >) =
1483+ if typeof< 'T>. IsValueType
1484+ then mkDelayedSeq ( fun () -> groupByValueType keyf seq)
1485+ else mkDelayedSeq ( fun () -> groupByRefType keyf seq)
14731486
14741487 [<CompiledName( " Distinct" ) >]
14751488 let distinct source =
0 commit comments