Faster optimized frozen dictionary creation (2/n) (#87630)
authorAdam Sitnik <adam.sitnik@gmail.com>
Fri, 16 Jun 2023 04:10:16 +0000 (06:10 +0200)
committerGitHub <noreply@github.com>
Fri, 16 Jun 2023 04:10:16 +0000 (00:10 -0400)
* use stackalloc for smaller sizes

* Revert "use stackalloc for smaller sizes", it did not help much

This reverts commit 204e1752d828fac0f772d144b0daaf7dd4ab93ec.

* let the caller of FrozenHashTable.Create provide the hashcodes, it avoids a closure, index boundary checks and helps with inlining (7% gain)

in case of hash set of integers it avoids renting and copying all integers to just have a copy of them

* keys and values are needed for every strategy, lets create them up-front (2-4% gain)

* in CreateLengthBucketsFrozenDictionaryIfAppropriate iterate over array rather than dictionary
* in OrdinalStringFrozenDictionary ctor avoid creating a copy of all entires (we already have a copy of keys)

* refactor: there is no need to pass entriesLength anymore (hashCodes.Length is the replacement)

21 files changed:
src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenDictionary.cs
src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenHashTable.cs
src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/Int32/Int32FrozenDictionary.cs
src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/Int32/Int32FrozenSet.cs
src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/ItemsFrozenSet.cs
src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/KeysAndValuesFrozenDictionary.cs
src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/KeyAnalyzer.cs
src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/LengthBucketsFrozenDictionary.cs
src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary.cs
src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary_Full.cs
src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary_FullCaseInsensitive.cs
src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary_FullCaseInsensitiveAscii.cs
src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary_LeftJustifiedCaseInsensitiveAsciiSubstring.cs
src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary_LeftJustifiedCaseInsensitiveSubstring.cs
src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary_LeftJustifiedSingleChar.cs
src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary_LeftJustifiedSubstring.cs
src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary_RightJustifiedCaseInsensitiveAsciiSubstring.cs
src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary_RightJustifiedCaseInsensitiveSubstring.cs
src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary_RightJustifiedSingleChar.cs
src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary_RightJustifiedSubstring.cs
src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenSet.cs

index 8582703..11a2db8 100644 (file)
@@ -229,15 +229,15 @@ namespace System.Collections.Frozen
             if (typeof(TKey) == typeof(string) &&
                 (ReferenceEquals(comparer, EqualityComparer<TKey>.Default) || ReferenceEquals(comparer, StringComparer.Ordinal) || ReferenceEquals(comparer, StringComparer.OrdinalIgnoreCase)))
             {
-                Dictionary<string, TValue> stringEntries = (Dictionary<string, TValue>)(object)source;
                 IEqualityComparer<string> stringComparer = (IEqualityComparer<string>)(object)comparer;
 
-                // this array is needed for every strategy
-                string[] entries = (string[])(object)source.Keys.ToArray();
+                // keys and values are needed for every strategy
+                string[] keys = (string[])(object)source.Keys.ToArray();
+                TValue[] values = source.Values.ToArray();
 
                 // Calculate the minimum and maximum lengths of the strings in the dictionary. Several of the analyses need this.
                 int minLength = int.MaxValue, maxLength = 0;
-                foreach (string key in entries)
+                foreach (string key in keys)
                 {
                     if (key.Length < minLength) minLength = key.Length;
                     if (key.Length > maxLength) maxLength = key.Length;
@@ -245,14 +245,14 @@ namespace System.Collections.Frozen
                 Debug.Assert(minLength >= 0 && maxLength >= minLength);
 
                 // Try to create an implementation that uses length buckets, where each bucket contains up to only a few strings of the same length.
-                FrozenDictionary<string, TValue>? frozenDictionary = LengthBucketsFrozenDictionary<TValue>.CreateLengthBucketsFrozenDictionaryIfAppropriate(stringEntries, stringComparer, minLength, maxLength, entries);
+                FrozenDictionary<string, TValue>? frozenDictionary = LengthBucketsFrozenDictionary<TValue>.CreateLengthBucketsFrozenDictionaryIfAppropriate(keys, values, stringComparer, minLength, maxLength);
                 if (frozenDictionary is not null)
                 {
                     return (FrozenDictionary<TKey, TValue>)(object)frozenDictionary;
                 }
 
                 // Analyze the keys for unique substrings and create an implementation that minimizes the cost of hashing keys.
-                KeyAnalyzer.AnalysisResults analysis = KeyAnalyzer.Analyze(entries, ReferenceEquals(stringComparer, StringComparer.OrdinalIgnoreCase), minLength, maxLength);
+                KeyAnalyzer.AnalysisResults analysis = KeyAnalyzer.Analyze(keys, ReferenceEquals(stringComparer, StringComparer.OrdinalIgnoreCase), minLength, maxLength);
                 if (analysis.SubstringHashing)
                 {
                     if (analysis.RightJustifiedSubstring)
@@ -260,14 +260,14 @@ namespace System.Collections.Frozen
                         if (analysis.IgnoreCase)
                         {
                             frozenDictionary = analysis.AllAsciiIfIgnoreCase
-                                ? new OrdinalStringFrozenDictionary_RightJustifiedCaseInsensitiveAsciiSubstring<TValue>(stringEntries, entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount)
-                                : new OrdinalStringFrozenDictionary_RightJustifiedCaseInsensitiveSubstring<TValue>(stringEntries, entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount);
+                                ? new OrdinalStringFrozenDictionary_RightJustifiedCaseInsensitiveAsciiSubstring<TValue>(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount)
+                                : new OrdinalStringFrozenDictionary_RightJustifiedCaseInsensitiveSubstring<TValue>(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount);
                         }
                         else
                         {
                             frozenDictionary = analysis.HashCount == 1
-                                ? new OrdinalStringFrozenDictionary_RightJustifiedSingleChar<TValue>(stringEntries, entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex)
-                                : new OrdinalStringFrozenDictionary_RightJustifiedSubstring<TValue>(stringEntries, entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount);
+                                ? new OrdinalStringFrozenDictionary_RightJustifiedSingleChar<TValue>(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex)
+                                : new OrdinalStringFrozenDictionary_RightJustifiedSubstring<TValue>(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount);
                         }
                     }
                     else
@@ -275,14 +275,14 @@ namespace System.Collections.Frozen
                         if (analysis.IgnoreCase)
                         {
                             frozenDictionary = analysis.AllAsciiIfIgnoreCase
-                                ? new OrdinalStringFrozenDictionary_LeftJustifiedCaseInsensitiveAsciiSubstring<TValue>(stringEntries, entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount)
-                                : new OrdinalStringFrozenDictionary_LeftJustifiedCaseInsensitiveSubstring<TValue>(stringEntries, entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount);
+                                ? new OrdinalStringFrozenDictionary_LeftJustifiedCaseInsensitiveAsciiSubstring<TValue>(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount)
+                                : new OrdinalStringFrozenDictionary_LeftJustifiedCaseInsensitiveSubstring<TValue>(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount);
                         }
                         else
                         {
                             frozenDictionary = analysis.HashCount == 1
-                                ? new OrdinalStringFrozenDictionary_LeftJustifiedSingleChar<TValue>(stringEntries, entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex)
-                                : new OrdinalStringFrozenDictionary_LeftJustifiedSubstring<TValue>(stringEntries, entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount);
+                                ? new OrdinalStringFrozenDictionary_LeftJustifiedSingleChar<TValue>(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex)
+                                : new OrdinalStringFrozenDictionary_LeftJustifiedSubstring<TValue>(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount);
                         }
                     }
                 }
@@ -291,12 +291,12 @@ namespace System.Collections.Frozen
                     if (analysis.IgnoreCase)
                     {
                         frozenDictionary = analysis.AllAsciiIfIgnoreCase
-                            ? new OrdinalStringFrozenDictionary_FullCaseInsensitiveAscii<TValue>(stringEntries, entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff)
-                            : new OrdinalStringFrozenDictionary_FullCaseInsensitive<TValue>(stringEntries, entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff);
+                            ? new OrdinalStringFrozenDictionary_FullCaseInsensitiveAscii<TValue>(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff)
+                            : new OrdinalStringFrozenDictionary_FullCaseInsensitive<TValue>(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff);
                     }
                     else
                     {
-                        frozenDictionary = new OrdinalStringFrozenDictionary_Full<TValue>(stringEntries, entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff);
+                        frozenDictionary = new OrdinalStringFrozenDictionary_Full<TValue>(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff);
                     }
                 }
 
index 72cf9ad..15ba747 100644 (file)
@@ -36,8 +36,7 @@ namespace System.Collections.Frozen
         }
 
         /// <summary>Initializes a frozen hash table.</summary>
-        /// <param name="entriesLength">The number of entries to track from the hash table.</param>
-        /// <param name="hashAtIndex">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.</param>
+        /// <param name="hashCodes">Pre-calculated hash codes.</param>
         /// <param name="storeDestIndexFromSrcIndex">A delegate that assigns the index to a specific entry. It's passed the destination and source indices.</param>
         /// <param name="optimizeForReading">true to spend additional effort tuning for subsequent read speed on the table; false to prioritize construction time.</param>
         /// <remarks>
@@ -48,18 +47,8 @@ namespace System.Collections.Frozen
         /// then uses this index to reference individual entries by indexing into <see cref="HashCodes"/>.
         /// </remarks>
         /// <returns>A frozen hash table.</returns>
-        public static FrozenHashTable Create(int entriesLength, Func<int, int> hashAtIndex, Action<int, int> storeDestIndexFromSrcIndex, bool optimizeForReading = true)
+        public static FrozenHashTable Create(ReadOnlySpan<int> hashCodes, Action<int, int> storeDestIndexFromSrcIndex, bool optimizeForReading = true)
         {
-            Debug.Assert(entriesLength != 0);
-
-            // Calculate the hashcodes for every entry.
-            int[] arrayPoolHashCodes = ArrayPool<int>.Shared.Rent(entriesLength);
-            Span<int> hashCodes = arrayPoolHashCodes.AsSpan(0, entriesLength);
-            for (int i = 0; i < entriesLength; i++)
-            {
-                hashCodes[i] = hashAtIndex(i);
-            }
-
             // Determine how many buckets to use.  This might be fewer than the number of entries
             // if any entries have identical hashcodes (not just different hashcodes that might
             // map to the same bucket).
@@ -124,7 +113,6 @@ namespace System.Collections.Frozen
             Debug.Assert(count == hashtableHashcodes.Length);
 
             ArrayPool<int>.Shared.Return(arrayPoolBuckets);
-            ArrayPool<int>.Shared.Return(arrayPoolHashCodes);
 
             return new FrozenHashTable(hashtableHashcodes, hashtableBuckets, fastModMultiplier);
         }
index bedcbb6..e987158 100644 (file)
@@ -1,6 +1,7 @@
 // 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.Runtime.CompilerServices;
@@ -27,10 +28,18 @@ namespace System.Collections.Frozen
 
             _values = new TValue[entries.Length];
 
+            int[] arrayPoolHashCodes = ArrayPool<int>.Shared.Rent(entries.Length);
+            Span<int> hashCodes = arrayPoolHashCodes.AsSpan(0, entries.Length);
+            for (int i = 0; i < entries.Length; i++)
+            {
+                hashCodes[i] = entries[i].Key;
+            }
+
             _hashTable = FrozenHashTable.Create(
-                entries.Length,
-                index => entries[index].Key,
+                hashCodes,
                 (destIndex, srcIndex) => _values[destIndex] = entries[srcIndex].Value);
+
+            ArrayPool<int>.Shared.Return(arrayPoolHashCodes);
         }
 
         /// <inheritdoc />
index 7f1da9d..4317b31 100644 (file)
@@ -26,9 +26,8 @@ namespace System.Collections.Frozen
             source.CopyTo(entries);
 
             _hashTable = FrozenHashTable.Create(
-                count,
-                index => entries[index],
-                delegate { });
+                new ReadOnlySpan<int>(entries, 0, count),
+                static delegate { });
 
             ArrayPool<int>.Shared.Return(entries);
         }
index abf9188..db94a1a 100644 (file)
@@ -1,6 +1,7 @@
 // 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;
 
@@ -22,11 +23,19 @@ namespace System.Collections.Frozen
 
             _items = new T[entries.Length];
 
+            int[] arrayPoolHashCodes = ArrayPool<int>.Shared.Rent(entries.Length);
+            Span<int> hashCodes = arrayPoolHashCodes.AsSpan(0, entries.Length);
+            for (int i = 0; i < entries.Length; i++)
+            {
+                hashCodes[i] = entries[i] is T t ? Comparer.GetHashCode(t) : 0;
+            }
+
             _hashTable = FrozenHashTable.Create(
-                entries.Length,
-                index => entries[index] is T t ? Comparer.GetHashCode(t) : 0,
+                hashCodes,
                 (destIndex, srcIndex) => _items[destIndex] = entries[srcIndex],
                 optimizeForReading);
+
+            ArrayPool<int>.Shared.Return(arrayPoolHashCodes);
         }
 
         /// <inheritdoc />
index fb168cf..ab0cba7 100644 (file)
@@ -1,6 +1,7 @@
 // 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;
 
@@ -24,15 +25,23 @@ namespace System.Collections.Frozen
             _keys = new TKey[entries.Length];
             _values = new TValue[entries.Length];
 
+            int[] arrayPoolHashCodes = ArrayPool<int>.Shared.Rent(entries.Length);
+            Span<int> hashCodes = arrayPoolHashCodes.AsSpan(0, entries.Length);
+            for (int i = 0; i < entries.Length; i++)
+            {
+                hashCodes[i] = Comparer.GetHashCode(entries[i].Key);
+            }
+
             _hashTable = FrozenHashTable.Create(
-                entries.Length,
-                index => Comparer.GetHashCode(entries[index].Key),
+                hashCodes,
                 (destIndex, srcIndex) =>
                 {
                     _keys[destIndex] = entries[srcIndex].Key;
                     _values[destIndex] = entries[srcIndex].Value;
                 },
                 optimizeForReading);
+
+            ArrayPool<int>.Shared.Return(arrayPoolHashCodes);
         }
 
         /// <inheritdoc />
index 4cdf5ba..82bd0b7 100644 (file)
@@ -225,7 +225,7 @@ namespace System.Collections.Frozen
         {
             set.Clear();
 
-            // SufficientUniquenessFactor of 95% is good enough.
+            // Sufficient uniqueness factor of 95% is good enough.
             // Instead of ensuring that 95% of data is good, we stop when we know that at least 5% is bad.
             int acceptableNonUniqueCount = uniqueStrings.Length / 20;
 
index 4a83ac0..ce0687b 100644 (file)
@@ -36,25 +36,25 @@ namespace System.Collections.Frozen
         }
 
         internal static LengthBucketsFrozenDictionary<TValue>? CreateLengthBucketsFrozenDictionaryIfAppropriate(
-            Dictionary<string, TValue> source, IEqualityComparer<string> comparer, int minLength, int maxLength, string[] keys)
+            string[] keys, TValue[] values, IEqualityComparer<string> comparer, int minLength, int maxLength)
         {
-            Debug.Assert(source.Count != 0);
+            Debug.Assert(keys.Length != 0 && keys.Length == values.Length);
             Debug.Assert(comparer == EqualityComparer<string>.Default || comparer == StringComparer.Ordinal || comparer == StringComparer.OrdinalIgnoreCase);
             Debug.Assert(minLength >= 0 && maxLength >= minLength);
 
             // If without even looking at the keys we know that some bucket will exceed the max per-bucket
             // limit (pigeon hole principle), we can early-exit out without doing any further work.
             int spread = maxLength - minLength + 1;
-            if (source.Count / spread > MaxPerLength)
+            if (keys.Length / spread > MaxPerLength)
             {
                 return null;
             }
 
             // Iterate through all of the inputs, bucketing them based on the length of the string.
             var groupedByLength = new Dictionary<int, List<KeyValuePair<string, TValue>>>();
-            foreach (KeyValuePair<string, TValue> pair in source)
+            for (int i = 0; i < keys.Length; i++)
             {
-                string s = pair.Key;
+                string s = keys[i];
                 Debug.Assert(s.Length >= minLength && s.Length <= maxLength);
 
 #if NET6_0_OR_GREATER
@@ -72,7 +72,7 @@ namespace System.Collections.Frozen
                     return null;
                 }
 
-                list.Add(pair);
+                list.Add(new KeyValuePair<string, TValue>(s, values[i]));
             }
 
             // If there would be too much empty space in the lookup array, bail.
@@ -81,7 +81,6 @@ namespace System.Collections.Frozen
                 return null;
             }
 
-            var values = new TValue[keys.Length];
             var lengthBuckets = new KeyValuePair<string, int>[spread][];
 
             // Iterate through each bucket, filling the keys/values arrays, and creating a lookup array such that
index b9f7dfd..2cb519e 100644 (file)
@@ -1,6 +1,7 @@
 // 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.Runtime.CompilerServices;
@@ -17,8 +18,8 @@ namespace System.Collections.Frozen
         private readonly int _maximumLengthDiff;
 
         internal OrdinalStringFrozenDictionary(
-            Dictionary<string, TValue> source,
             string[] keys,
+            TValue[] values,
             IEqualityComparer<string> comparer,
             int minimumLength,
             int maximumLengthDiff,
@@ -26,28 +27,35 @@ namespace System.Collections.Frozen
             int hashCount = -1) :
             base(comparer)
         {
-            Debug.Assert(source.Count != 0);
+            Debug.Assert(keys.Length != 0 && keys.Length == values.Length);
             Debug.Assert(comparer == EqualityComparer<string>.Default || comparer == StringComparer.Ordinal || comparer == StringComparer.OrdinalIgnoreCase);
 
-            var entries = new KeyValuePair<string, TValue>[source.Count];
-            ((ICollection<KeyValuePair<string, TValue>>)source).CopyTo(entries, 0);
+            // we need an extra copy, as the order of items will change
+            _keys = new string[keys.Length];
+            _values = new TValue[values.Length];
 
-            _keys = keys;
-            _values = new TValue[entries.Length];
             _minimumLength = minimumLength;
             _maximumLengthDiff = maximumLengthDiff;
 
             HashIndex = hashIndex;
             HashCount = hashCount;
 
+            int[] arrayPoolHashCodes = ArrayPool<int>.Shared.Rent(keys.Length);
+            Span<int> hashCodes = arrayPoolHashCodes.AsSpan(0, keys.Length);
+            for (int i = 0; i < keys.Length; i++)
+            {
+                hashCodes[i] = GetHashCode(keys[i]);
+            }
+
             _hashTable = FrozenHashTable.Create(
-                entries.Length,
-                index => GetHashCode(entries[index].Key),
+                hashCodes,
                 (destIndex, srcIndex) =>
                 {
-                    _keys[destIndex] = entries[srcIndex].Key;
-                    _values[destIndex] = entries[srcIndex].Value;
+                    _keys[destIndex] = keys[srcIndex];
+                    _values[destIndex] = values[srcIndex];
                 });
+
+            ArrayPool<int>.Shared.Return(arrayPoolHashCodes);
         }
 
         private protected int HashIndex { get; }
index 9ef93ca..56ce7ff 100644 (file)
@@ -8,12 +8,12 @@ namespace System.Collections.Frozen
     internal sealed class OrdinalStringFrozenDictionary_Full<TValue> : OrdinalStringFrozenDictionary<TValue>
     {
         internal OrdinalStringFrozenDictionary_Full(
-            Dictionary<string, TValue> source,
             string[] keys,
+            TValue[] values,
             IEqualityComparer<string> comparer,
             int minimumLength,
             int maximumLengthDiff)
-            : base(source, keys, comparer, minimumLength, maximumLengthDiff)
+            : base(keys, values, comparer, minimumLength, maximumLengthDiff)
         {
         }
 
index 0ec65c0..3f09ba5 100644 (file)
@@ -8,12 +8,12 @@ namespace System.Collections.Frozen
     internal sealed class OrdinalStringFrozenDictionary_FullCaseInsensitive<TValue> : OrdinalStringFrozenDictionary<TValue>
     {
         internal OrdinalStringFrozenDictionary_FullCaseInsensitive(
-            Dictionary<string, TValue> source,
             string[] keys,
+            TValue[] values,
             IEqualityComparer<string> comparer,
             int minimumLength,
             int maximumLengthDiff)
-            : base(source, keys, comparer, minimumLength, maximumLengthDiff)
+            : base(keys, values, comparer, minimumLength, maximumLengthDiff)
         {
         }
 
index c814f2b..b029567 100644 (file)
@@ -8,12 +8,12 @@ namespace System.Collections.Frozen
     internal sealed class OrdinalStringFrozenDictionary_FullCaseInsensitiveAscii<TValue> : OrdinalStringFrozenDictionary<TValue>
     {
         internal OrdinalStringFrozenDictionary_FullCaseInsensitiveAscii(
-            Dictionary<string, TValue> source,
             string[] keys,
+            TValue[] values,
             IEqualityComparer<string> comparer,
             int minimumLength,
             int maximumLengthDiff)
-            : base(source, keys, comparer, minimumLength, maximumLengthDiff)
+            : base(keys, values, comparer, minimumLength, maximumLengthDiff)
         {
         }
 
index 166ae6a..401b0f2 100644 (file)
@@ -8,14 +8,14 @@ namespace System.Collections.Frozen
     internal sealed class OrdinalStringFrozenDictionary_LeftJustifiedCaseInsensitiveAsciiSubstring<TValue> : OrdinalStringFrozenDictionary<TValue>
     {
         internal OrdinalStringFrozenDictionary_LeftJustifiedCaseInsensitiveAsciiSubstring(
-            Dictionary<string, TValue> source,
             string[] keys,
+            TValue[] values,
             IEqualityComparer<string> comparer,
             int minimumLength,
             int maximumLengthDiff,
             int hashIndex,
             int hashCount)
-            : base(source, keys, comparer, minimumLength, maximumLengthDiff, hashIndex, hashCount)
+            : base(keys, values, comparer, minimumLength, maximumLengthDiff, hashIndex, hashCount)
         {
         }
 
index dad71c4..c533441 100644 (file)
@@ -8,14 +8,14 @@ namespace System.Collections.Frozen
     internal sealed class OrdinalStringFrozenDictionary_LeftJustifiedCaseInsensitiveSubstring<TValue> : OrdinalStringFrozenDictionary<TValue>
     {
         internal OrdinalStringFrozenDictionary_LeftJustifiedCaseInsensitiveSubstring(
-            Dictionary<string, TValue> source,
             string[] keys,
+            TValue[] values,
             IEqualityComparer<string> comparer,
             int minimumLength,
             int maximumLengthDiff,
             int hashIndex,
             int hashCount)
-            : base(source, keys, comparer, minimumLength, maximumLengthDiff, hashIndex, hashCount)
+            : base(keys, values, comparer, minimumLength, maximumLengthDiff, hashIndex, hashCount)
         {
         }
 
index ca03320..b2bb0bb 100644 (file)
@@ -8,13 +8,13 @@ namespace System.Collections.Frozen
     internal sealed class OrdinalStringFrozenDictionary_LeftJustifiedSingleChar<TValue> : OrdinalStringFrozenDictionary<TValue>
     {
         internal OrdinalStringFrozenDictionary_LeftJustifiedSingleChar(
-            Dictionary<string, TValue> source,
             string[] keys,
+            TValue[] values,
             IEqualityComparer<string> comparer,
             int minimumLength,
             int maximumLengthDiff,
             int hashIndex)
-            : base(source, keys, comparer, minimumLength, maximumLengthDiff, hashIndex, 1)
+            : base(keys, values, comparer, minimumLength, maximumLengthDiff, hashIndex, 1)
         {
         }
 
index d48e473..2812dde 100644 (file)
@@ -8,14 +8,14 @@ namespace System.Collections.Frozen
     internal sealed class OrdinalStringFrozenDictionary_LeftJustifiedSubstring<TValue> : OrdinalStringFrozenDictionary<TValue>
     {
         internal OrdinalStringFrozenDictionary_LeftJustifiedSubstring(
-            Dictionary<string, TValue> source,
             string[] keys,
+            TValue[] values,
             IEqualityComparer<string> comparer,
             int minimumLength,
             int maximumLengthDiff,
             int hashIndex,
             int hashCount)
-            : base(source, keys, comparer, minimumLength, maximumLengthDiff, hashIndex, hashCount)
+            : base(keys, values, comparer, minimumLength, maximumLengthDiff, hashIndex, hashCount)
         {
         }
 
index b26d2a1..22cf6b6 100644 (file)
@@ -8,14 +8,14 @@ namespace System.Collections.Frozen
     internal sealed class OrdinalStringFrozenDictionary_RightJustifiedCaseInsensitiveAsciiSubstring<TValue> : OrdinalStringFrozenDictionary<TValue>
     {
         internal OrdinalStringFrozenDictionary_RightJustifiedCaseInsensitiveAsciiSubstring(
-            Dictionary<string, TValue> source,
             string[] keys,
+            TValue[] values,
             IEqualityComparer<string> comparer,
             int minimumLength,
             int maximumLengthDiff,
             int hashIndex,
             int hashCount)
-            : base(source, keys, comparer, minimumLength, maximumLengthDiff, hashIndex, hashCount)
+            : base(keys, values, comparer, minimumLength, maximumLengthDiff, hashIndex, hashCount)
         {
         }
 
index fc348bc..a2fec24 100644 (file)
@@ -8,14 +8,14 @@ namespace System.Collections.Frozen
     internal sealed class OrdinalStringFrozenDictionary_RightJustifiedCaseInsensitiveSubstring<TValue> : OrdinalStringFrozenDictionary<TValue>
     {
         internal OrdinalStringFrozenDictionary_RightJustifiedCaseInsensitiveSubstring(
-            Dictionary<string, TValue> source,
             string[] keys,
+            TValue[] values,
             IEqualityComparer<string> comparer,
             int minimumLength,
             int maximumLengthDiff,
             int hashIndex,
             int hashCount)
-            : base(source, keys, comparer, minimumLength, maximumLengthDiff, hashIndex, hashCount)
+            : base(keys, values, comparer, minimumLength, maximumLengthDiff, hashIndex, hashCount)
         {
         }
 
index 60c8ecc..cb7ae5f 100644 (file)
@@ -8,13 +8,13 @@ namespace System.Collections.Frozen
     internal sealed class OrdinalStringFrozenDictionary_RightJustifiedSingleChar<TValue> : OrdinalStringFrozenDictionary<TValue>
     {
         internal OrdinalStringFrozenDictionary_RightJustifiedSingleChar(
-            Dictionary<string, TValue> source,
             string[] keys,
+            TValue[] values,
             IEqualityComparer<string> comparer,
             int minimumLength,
             int maximumLengthDiff,
             int hashIndex)
-            : base(source, keys, comparer, minimumLength, maximumLengthDiff, hashIndex, 1)
+            : base(keys, values, comparer, minimumLength, maximumLengthDiff, hashIndex, 1)
         {
         }
 
index f4dd7ad..cd8fe06 100644 (file)
@@ -8,14 +8,14 @@ namespace System.Collections.Frozen
     internal sealed class OrdinalStringFrozenDictionary_RightJustifiedSubstring<TValue> : OrdinalStringFrozenDictionary<TValue>
     {
         internal OrdinalStringFrozenDictionary_RightJustifiedSubstring(
-            Dictionary<string, TValue> source,
             string[] keys,
+            TValue[] values,
             IEqualityComparer<string> comparer,
             int minimumLength,
             int maximumLengthDiff,
             int hashIndex,
             int hashCount)
-            : base(source, keys, comparer, minimumLength, maximumLengthDiff, hashIndex, hashCount)
+            : base(keys, values, comparer, minimumLength, maximumLengthDiff, hashIndex, hashCount)
         {
         }
 
index f4c7700..1ee4b27 100644 (file)
@@ -1,6 +1,7 @@
 // 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.Runtime.CompilerServices;
 
@@ -30,10 +31,18 @@ namespace System.Collections.Frozen
             HashIndex = hashIndex;
             HashCount = hashCount;
 
+            int[] arrayPoolHashCodes = ArrayPool<int>.Shared.Rent(entries.Length);
+            Span<int> hashCodes = arrayPoolHashCodes.AsSpan(0, entries.Length);
+            for (int i = 0; i < entries.Length; i++)
+            {
+                hashCodes[i] = GetHashCode(entries[i]);
+            }
+
             _hashTable = FrozenHashTable.Create(
-                entries.Length,
-                index => GetHashCode(entries[index]),
+                hashCodes,
                 (destIndex, srcIndex) => _items[destIndex] = entries[srcIndex]);
+
+            ArrayPool<int>.Shared.Return(arrayPoolHashCodes);
         }
 
         private protected int HashIndex { get; }