Skip to content

Commit

Permalink
Make FrozenHashTable non-generic (#81603)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
stephentoub committed Feb 4, 2023
1 parent efe93b7 commit 112c758
Show file tree
Hide file tree
Showing 7 changed files with 40 additions and 35 deletions.
Expand Up @@ -36,29 +36,28 @@ private FrozenHashTable(int[] hashCodes, Bucket[] buckets, ulong fastModMultipli
}

/// <summary>Initializes a frozen hash table.</summary>
/// <param name="entries">The set of entries to track from the hash table.</param>
/// <param name="hasher">A delegate that produces a hash code for a given entry.</param>
/// <param name="setter">A delegate that assigns the index to a specific entry.</param>
/// <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="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>
/// <typeparam name="T">The type of elements in the hash table.</typeparam>
/// <remarks>
/// 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 <paramref name="setter"/> to indicate
/// bucket table. In the process of doing so, it calls out to the <paramref name="storeDestIndexFromSrcIndex"/> to indicate
/// the resulting index for that entry. <see cref="FindMatchingEntries(int, out int, out int)"/>
/// 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<T>(T[] entries, Func<T, int> hasher, Action<int, T> setter, bool optimizeForReading = true)
public static FrozenHashTable Create(int entriesLength, Func<int, int> hashAtIndex, Action<int, int> storeDestIndexFromSrcIndex, bool optimizeForReading = true)
{
Debug.Assert(entries.Length != 0);
Debug.Assert(entriesLength != 0);

// Calculate the hashcodes for every entry.
int[] arrayPoolHashCodes = ArrayPool<int>.Shared.Rent(entries.Length);
Span<int> hashCodes = arrayPoolHashCodes.AsSpan(0, entries.Length);
for (int i = 0; i < entries.Length; i++)
int[] arrayPoolHashCodes = ArrayPool<int>.Shared.Rent(entriesLength);
Span<int> 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
Expand Down Expand Up @@ -113,7 +112,7 @@ public static FrozenHashTable Create<T>(T[] entries, Func<T, int> hasher, Action
while (index >= 0)
{
hashtableHashcodes[count] = hashCodes[index];
setter(count, entries[index]);
storeDestIndexFromSrcIndex(count, index);
count++;
bucketCount++;

Expand Down
Expand Up @@ -28,9 +28,9 @@ internal Int32FrozenDictionary(Dictionary<int, TValue> 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);
}

/// <inheritdoc />
Expand Down
@@ -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
{
Expand All @@ -21,10 +21,16 @@ internal Int32FrozenSet(HashSet<int> source) : base(EqualityComparer<int>.Defaul
Debug.Assert(source.Count != 0);
Debug.Assert(ReferenceEquals(source.Comparer, EqualityComparer<int>.Default));

int count = source.Count;
int[] entries = ArrayPool<int>.Shared.Rent(count);
source.CopyTo(entries);

_hashTable = FrozenHashTable.Create(
source.ToArray(),
item => item,
(_, _) => { });
count,
index => entries[index],
delegate { });

ArrayPool<int>.Shared.Return(entries);
}

/// <inheritdoc />
Expand Down
Expand Up @@ -23,9 +23,9 @@ protected ItemsFrozenSet(HashSet<T> 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);
}

Expand Down
Expand Up @@ -25,12 +25,12 @@ protected KeysAndValuesFrozenDictionary(Dictionary<TKey, TValue> 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);
}
Expand Down
Expand Up @@ -41,12 +41,12 @@ internal abstract class OrdinalStringFrozenDictionary<TValue> : 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;
});
}

Expand Down
Expand Up @@ -31,9 +31,9 @@ internal abstract class OrdinalStringFrozenSet : FrozenSetInternalBase<string, O
HashCount = hashCount;

_hashTable = FrozenHashTable.Create(
entries,
GetHashCode,
(index, item) => _items[index] = item);
entries.Length,
index => GetHashCode(entries[index]),
(destIndex, srcIndex) => _items[destIndex] = entries[srcIndex]);
}

private protected int HashIndex { get; }
Expand Down

0 comments on commit 112c758

Please sign in to comment.