From ede2cdbf83f99f055d0dff354289a1d868138cc0 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Fri, 3 Feb 2023 12:18:19 -0500 Subject: [PATCH] Make FrozenHashTable non-generic With some tweaks to how it's defined, we can avoid making it generic at all, which helps to reduce native aot compilation size when ToFrozenDictionary/Set is used with multiple generic instantiations. --- .../Collections/Frozen/FrozenHashTable.cs | 23 +++++++++---------- .../Frozen/Int32/Int32FrozenDictionary.cs | 6 ++--- .../Frozen/Int32/Int32FrozenSet.cs | 14 +++++++---- .../Collections/Frozen/ItemsFrozenSet.cs | 6 ++--- .../Frozen/KeysAndValuesFrozenDictionary.cs | 10 ++++---- .../String/OrdinalStringFrozenDictionary.cs | 10 ++++---- .../Frozen/String/OrdinalStringFrozenSet.cs | 6 ++--- 7 files changed, 40 insertions(+), 35 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenHashTable.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenHashTable.cs index 5ad2bdebda39..0e12af30275c 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenHashTable.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenHashTable.cs @@ -36,29 +36,28 @@ private FrozenHashTable(int[] hashCodes, Bucket[] buckets, ulong fastModMultipli } /// Initializes a frozen hash table. - /// The set of entries to track from the hash table. - /// A delegate that produces a hash code for a given entry. - /// A delegate that assigns the index to a specific entry. + /// The number of entries to track from the hash table. + /// A delegate that produces a hash code for a given entry. It's passed the index of the entry and returns that entry's hash code. + /// A delegate that assigns the index to a specific entry. It's passed the destination and source indices. /// true to spend additional effort tuning for subsequent read speed on the table; false to prioritize construction time. - /// The type of elements in the hash table. /// /// This method will iterate through the incoming entries and will invoke the hasher on each once. /// It will then determine the optimal number of hash buckets to allocate and will populate the - /// bucket table. In the process of doing so, it calls out to the to indicate + /// bucket table. In the process of doing so, it calls out to the to indicate /// the resulting index for that entry. /// then uses this index to reference individual entries by indexing into . /// /// A frozen hash table. - public static FrozenHashTable Create(T[] entries, Func hasher, Action setter, bool optimizeForReading = true) + public static FrozenHashTable Create(int entriesLength, Func hashAtIndex, Action storeDestIndexFromSrcIndex, bool optimizeForReading = true) { - Debug.Assert(entries.Length != 0); + Debug.Assert(entriesLength != 0); // Calculate the hashcodes for every entry. - int[] arrayPoolHashCodes = ArrayPool.Shared.Rent(entries.Length); - Span hashCodes = arrayPoolHashCodes.AsSpan(0, entries.Length); - for (int i = 0; i < entries.Length; i++) + int[] arrayPoolHashCodes = ArrayPool.Shared.Rent(entriesLength); + Span hashCodes = arrayPoolHashCodes.AsSpan(0, entriesLength); + for (int i = 0; i < entriesLength; i++) { - hashCodes[i] = hasher(entries[i]); + hashCodes[i] = hashAtIndex(i); } // Determine how many buckets to use. This might be fewer than the number of entries @@ -113,7 +112,7 @@ public static FrozenHashTable Create(T[] entries, Func hasher, Action while (index >= 0) { hashtableHashcodes[count] = hashCodes[index]; - setter(count, entries[index]); + storeDestIndexFromSrcIndex(count, index); count++; bucketCount++; diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/Int32/Int32FrozenDictionary.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/Int32/Int32FrozenDictionary.cs index 43214afba91a..bedcbb6bd080 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/Int32/Int32FrozenDictionary.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/Int32/Int32FrozenDictionary.cs @@ -28,9 +28,9 @@ internal Int32FrozenDictionary(Dictionary source) : base(EqualityCo _values = new TValue[entries.Length]; _hashTable = FrozenHashTable.Create( - entries, - pair => pair.Key, - (index, pair) => _values[index] = pair.Value); + entries.Length, + index => entries[index].Key, + (destIndex, srcIndex) => _values[destIndex] = entries[srcIndex].Value); } /// diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/Int32/Int32FrozenSet.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/Int32/Int32FrozenSet.cs index 7e474864d273..7f1da9dfa552 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/Int32/Int32FrozenSet.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/Int32/Int32FrozenSet.cs @@ -1,9 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Buffers; using System.Collections.Generic; using System.Diagnostics; -using System.Linq; namespace System.Collections.Frozen { @@ -21,10 +21,16 @@ internal Int32FrozenSet(HashSet source) : base(EqualityComparer.Defaul Debug.Assert(source.Count != 0); Debug.Assert(ReferenceEquals(source.Comparer, EqualityComparer.Default)); + int count = source.Count; + int[] entries = ArrayPool.Shared.Rent(count); + source.CopyTo(entries); + _hashTable = FrozenHashTable.Create( - source.ToArray(), - item => item, - (_, _) => { }); + count, + index => entries[index], + delegate { }); + + ArrayPool.Shared.Return(entries); } /// diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/ItemsFrozenSet.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/ItemsFrozenSet.cs index 61fdf6a4ee13..abf91889fe46 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/ItemsFrozenSet.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/ItemsFrozenSet.cs @@ -23,9 +23,9 @@ protected ItemsFrozenSet(HashSet source, bool optimizeForReading = true) : ba _items = new T[entries.Length]; _hashTable = FrozenHashTable.Create( - entries, - o => o is null ? 0 : Comparer.GetHashCode(o), - (index, item) => _items[index] = item, + entries.Length, + index => entries[index] is T t ? Comparer.GetHashCode(t) : 0, + (destIndex, srcIndex) => _items[destIndex] = entries[srcIndex], optimizeForReading); } diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/KeysAndValuesFrozenDictionary.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/KeysAndValuesFrozenDictionary.cs index f3bd4952ce49..fb168cf1b65e 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/KeysAndValuesFrozenDictionary.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/KeysAndValuesFrozenDictionary.cs @@ -25,12 +25,12 @@ protected KeysAndValuesFrozenDictionary(Dictionary source, bool op _values = new TValue[entries.Length]; _hashTable = FrozenHashTable.Create( - entries, - pair => Comparer.GetHashCode(pair.Key), - (index, pair) => + entries.Length, + index => Comparer.GetHashCode(entries[index].Key), + (destIndex, srcIndex) => { - _keys[index] = pair.Key; - _values[index] = pair.Value; + _keys[destIndex] = entries[srcIndex].Key; + _values[destIndex] = entries[srcIndex].Value; }, optimizeForReading); } diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary.cs index 4ff5c907486e..b9f7dfd89cd1 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary.cs @@ -41,12 +41,12 @@ internal abstract class OrdinalStringFrozenDictionary : FrozenDictionary HashCount = hashCount; _hashTable = FrozenHashTable.Create( - entries, - pair => GetHashCode(pair.Key), - (index, pair) => + entries.Length, + index => GetHashCode(entries[index].Key), + (destIndex, srcIndex) => { - _keys[index] = pair.Key; - _values[index] = pair.Value; + _keys[destIndex] = entries[srcIndex].Key; + _values[destIndex] = entries[srcIndex].Value; }); } diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenSet.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenSet.cs index bd1ee3cad40a..f4c7700f0dd8 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenSet.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenSet.cs @@ -31,9 +31,9 @@ internal abstract class OrdinalStringFrozenSet : FrozenSetInternalBase _items[index] = item); + entries.Length, + index => GetHashCode(entries[index]), + (destIndex, srcIndex) => _items[destIndex] = entries[srcIndex]); } private protected int HashIndex { get; }