Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Commit

Permalink
Dictionary magic remainder rather than mod
Browse files Browse the repository at this point in the history
  • Loading branch information
benaadams committed Dec 19, 2017
1 parent b50667f commit a8917c0
Show file tree
Hide file tree
Showing 2 changed files with 192 additions and 27 deletions.
60 changes: 33 additions & 27 deletions src/mscorlib/shared/System/Collections/Generic/Dictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ private struct Entry
private ValueCollection _values;
private object _syncRoot;

uint _magic;
int _shift;

// constants for serialization
private const string VersionName = "Version"; // Do not rename (binary serialization)
private const string HashSizeName = "HashSize"; // Do not rename (binary serialization). Must save buckets.Length
Expand Down Expand Up @@ -369,8 +372,8 @@ private int FindEntry(TKey key)
{
IEqualityComparer<TKey> comparer = _comparer;
int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF;
i = buckets[hashCode % buckets.Length];

i = buckets[HashHelpers.MagicNumberRemainder(hashCode, buckets.Length, _magic, _shift)];
Entry[] entries = _entries;
do
{
Expand Down Expand Up @@ -399,7 +402,7 @@ private int FindEntryDefaultComparer(TKey key)
if (buckets != null)
{
int hashCode = key.GetHashCode() & 0x7FFFFFFF;
i = buckets[hashCode % buckets.Length];
i = buckets[HashHelpers.MagicNumberRemainder(hashCode, buckets.Length, _magic, _shift)];

Entry[] entries = _entries;
do
Expand All @@ -419,16 +422,16 @@ private int FindEntryDefaultComparer(TKey key)

private void Initialize(int capacity)
{
int size = HashHelpers.GetPrime(capacity);
int[] buckets = new int[size];
HashHelpers.NearestPrimeInfo(capacity, out int prime, out _magic, out _shift);
int[] buckets = new int[prime];
for (int i = 0; i < buckets.Length; i++)
{
buckets[i] = -1;
}

_freeList = -1;
_buckets = buckets;
_entries = new Entry[size];
_entries = new Entry[prime];
}

private bool TryInsert(TKey key, TValue value, InsertionBehavior behavior)
Expand All @@ -446,7 +449,7 @@ private bool TryInsert(TKey key, TValue value, InsertionBehavior behavior)
int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF;
int collisionCount = 0;

ref int bucket = ref _buckets[hashCode % _buckets.Length];
ref int bucket = ref _buckets[HashHelpers.MagicNumberRemainder(hashCode, _buckets.Length, _magic, _shift)];
int i = bucket;
Entry[] entries = _entries;
do
Expand Down Expand Up @@ -495,15 +498,15 @@ private bool TryInsert(TKey key, TValue value, InsertionBehavior behavior)
int count = _count;
if (count == entries.Length)
{
Resize();
Resize(forceNewHashCodes: false);
resized = true;
}
index = count;
_count = count + 1;
entries = _entries;
}

ref int targetBucket = ref resized ? ref _buckets[hashCode % _buckets.Length] : ref bucket;
ref int targetBucket = ref resized ? ref _buckets[HashHelpers.MagicNumberRemainder(hashCode, _buckets.Length, _magic, _shift)] : ref bucket;
ref Entry entry = ref entries[index];

if (updateFreeList)
Expand All @@ -523,7 +526,7 @@ private bool TryInsert(TKey key, TValue value, InsertionBehavior behavior)
// If we hit the collision threshold we'll need to switch to the comparer which is using randomized string hashing
// i.e. EqualityComparer<string>.Default.
_comparer = null;
Resize(entries.Length, true);
Resize(forceNewHashCodes: true);
}

return true;
Expand All @@ -543,7 +546,7 @@ private bool TryInsertDefaultComparer(TKey key, TValue value, InsertionBehavior
int hashCode = key.GetHashCode() & 0x7FFFFFFF;
int collisionCount = 0;

ref int bucket = ref _buckets[hashCode % _buckets.Length];
ref int bucket = ref _buckets[HashHelpers.MagicNumberRemainder(hashCode, _buckets.Length, _magic, _shift)];
int i = bucket;
Entry[] entries = _entries;
do
Expand Down Expand Up @@ -592,15 +595,15 @@ private bool TryInsertDefaultComparer(TKey key, TValue value, InsertionBehavior
int count = _count;
if (count == entries.Length)
{
Resize();
Resize(forceNewHashCodes: false);
resized = true;
}
index = count;
_count = count + 1;
entries = _entries;
}

ref int targetBucket = ref resized ? ref _buckets[hashCode % _buckets.Length] : ref bucket;
ref int targetBucket = ref resized ? ref _buckets[HashHelpers.MagicNumberRemainder(hashCode, _buckets.Length, _magic, _shift)] : ref bucket;
ref Entry entry = ref entries[index];

if (updateFreeList)
Expand Down Expand Up @@ -663,25 +666,26 @@ public virtual void OnDeserialization(object sender)
HashHelpers.SerializationInfoTable.Remove(this);
}

private void Resize()
{
Resize(HashHelpers.ExpandPrime(_count), false);
}

private void Resize(int newSize, bool forceNewHashCodes)
private void Resize(bool forceNewHashCodes)
{
// Value types never rehash
Debug.Assert(!forceNewHashCodes || default(TKey) == null);
Debug.Assert(newSize >= _entries.Length);
Debug.Assert(default(TKey) == null || !forceNewHashCodes);

int[] buckets = new int[newSize];
int count = _count;
int prime = count;
if (default(TKey) != null || !forceNewHashCodes)
{
HashHelpers.ExpandPrimeInfo(prime, out prime, out _magic, out _shift);
Debug.Assert(prime > _entries.Length);
}

int[] buckets = new int[prime];
for (int i = 0; i < buckets.Length; i++)
{
buckets[i] = -1;
}
Entry[] entries = new Entry[newSize];
Entry[] entries = new Entry[prime];

int count = _count;
Array.Copy(_entries, 0, entries, 0, count);

if (default(TKey) == null && forceNewHashCodes)
Expand All @@ -696,11 +700,13 @@ private void Resize(int newSize, bool forceNewHashCodes)
}
}

uint magic = _magic;
int shift = _shift;
for (int i = 0; i < count; i++)
{
if (entries[i].hashCode >= 0)
{
int bucket = entries[i].hashCode % newSize;
int bucket = HashHelpers.MagicNumberRemainder(entries[i].hashCode, prime, magic, shift);
entries[i].next = buckets[bucket];
buckets[bucket] = i;
}
Expand All @@ -723,7 +729,7 @@ public bool Remove(TKey key)
if (_buckets != null)
{
int hashCode = (_comparer?.GetHashCode(key) ?? key.GetHashCode()) & 0x7FFFFFFF;
int bucket = hashCode % _buckets.Length;
int bucket = HashHelpers.MagicNumberRemainder(hashCode, _buckets.Length, _magic, _shift);
int last = -1;
int i = _buckets[bucket];
while (i >= 0)
Expand Down Expand Up @@ -777,7 +783,7 @@ public bool Remove(TKey key, out TValue value)
if (_buckets != null)
{
int hashCode = (_comparer?.GetHashCode(key) ?? key.GetHashCode()) & 0x7FFFFFFF;
int bucket = hashCode % _buckets.Length;
int bucket = HashHelpers.MagicNumberRemainder(hashCode, _buckets.Length, _magic, _shift);
int last = -1;
int i = _buckets[bucket];
while (i >= 0)
Expand Down
159 changes: 159 additions & 0 deletions src/mscorlib/shared/System/Collections/HashHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Threading;
using Internal.Runtime.CompilerServices;

namespace System.Collections
{
Expand Down Expand Up @@ -33,6 +35,92 @@ internal static class HashHelpers
187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263,
1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369};

public static readonly uint[] PrimeMagicShift =
{
// prime, magic multiplier, magic shift
3, 0x55555556, 32,
7, 0x92492493, 34,
11, 0x2e8ba2e9, 33,
17, 0x78787879, 35,
23, 0xb21642c9, 36,
29, 0x8d3dcb09, 36,
37, 0xdd67c8a7, 37,
47, 0xae4c415d, 37,
59, 0x22b63cbf, 35,
71, 0xe6c2b449, 38,
89, 0xb81702e1, 38,
107, 0x4c8f8d29, 37,
131, 0x3e88cb3d, 37,
163, 0x0c907da5, 35,
197, 0x532ae21d, 38,
239, 0x891ac73b, 39,
293, 0xdfac1f75, 40,
353, 0xb9a7862b, 40,
431, 0x980e4157, 40,
521, 0x3ee4f99d, 39,
631, 0x33ee2623, 39,
761, 0x561e46a5, 40,
919, 0x8e9fe543, 41,
1103, 0x1db54401, 39,
1327, 0xc58bdd47, 42,
1597, 0xa425d4b9, 42,
1931, 0x10f82d9b, 39,
2333, 0x705d0d0f, 42,
2801, 0x2ecb7285, 41,
3371, 0x9b876783, 43,
4049, 0x817c5d53, 43,
4861, 0x35ed914d, 42,
5839, 0x0b394d8f, 40,
7013, 0x9584d635, 44,
8419, 0x7c8c7b75, 44,
10103, 0x33e4f01d, 43,
12143, 0x565a3073, 44,
14591, 0x23eeaa5d, 43,
17519, 0x77b510e9, 45,
21023, 0x63c14fe5, 45,
25229, 0x531fe999, 45,
30293, 0x8a75366b, 46,
36353, 0xe6c11447, 47,
43627, 0xc047bac3, 47,
52361, 0x0a035099, 43,
62851, 0x42bbed05, 46,
75431, 0x379ac159, 46,
90523, 0x05cab127, 43,
108631, 0x9a713743, 48,
130363, 0x80b236c9, 48,
156437, 0x6b3eeec1, 48,
187751, 0xb2b7bcf9, 49,
225307, 0x4a76bbc7, 48,
270371, 0x7c1aeabf, 49,
324449, 0x676b743d, 49,
389357, 0x2b16ec6d, 48,
467237, 0x8fa1117f, 50,
560689, 0x77b0a38f, 50,
672827, 0x63bddbb1, 50,
807403, 0x0531def9, 46,
968897, 0x8a86bc61, 51,
1162687, 0x737002ad, 51,
1395263, 0x180c7f9f, 49,
1674319, 0x140a67af, 49,
2009191, 0x42cd47bf, 51,
2411033, 0x37ab0b5f, 51,
2893249, 0x5cc7a9dd, 52,
3471899, 0x4d510d43, 52,
4166287, 0x080dc5ad, 49,
4999559, 0x035b11b9, 48,
5999471, 0xb2f90627, 54,
7199369, 0x9524d54d, 54,
14398753, 0x4a92658f, 54,
28797523, 0x4a9262ad, 55,
57595063, 0x9524c277, 57,
115190149, 0x9524c083, 58,
230380307, 0x4a926011, 58,
460760623, 0x9524bff1, 60,
921521257, 0x9524bfd3, 61,
1843042529, 0x4a925fdf, 61,
2146435069, 0x20040081, 60
};

public static bool IsPrime(int candidate)
{
if ((candidate & 1) != 0)
Expand Down Expand Up @@ -104,5 +192,76 @@ internal static ConditionalWeakTable<object, SerializationInfo> SerializationInf
return s_serializationInfoTable;
}
}

// To implement magic-number divide with a 32-bit magic number,
// multiply by the magic number, take the top 64 bits, and shift that
// by the amount given in the table.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static uint MagicNumberDivide(uint numerator, uint magic, int shift)
{
return (uint)((numerator * (ulong)magic) >> shift);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int MagicNumberRemainder(int numerator, int divisor, uint magic, int shift)
{
Debug.Assert(numerator >= 0);
uint product = MagicNumberDivide((uint)numerator, magic, shift);
Debug.Assert(product == numerator / divisor);
int result = (int)(numerator - (product * divisor));
Debug.Assert(result == numerator % divisor);
return result;
}

[StructLayout(LayoutKind.Sequential)]
public readonly struct PrimeInfo
{
public readonly int Prime;
public readonly uint Magic;
public readonly int Shift;
}

public static void NearestPrimeInfo(int min, out int prime, out uint magic, out int shift)
{
if (min < 0)
{
throw new ArgumentException(SR.Arg_HTCapacityOverflow);
}

uint[] primeMagicShift = PrimeMagicShift;
for (int i = 0; i < primeMagicShift.Length; i += 3)
{
ref uint primeRef = ref primeMagicShift[i];
if (primeRef >= min)
{
PrimeInfo primeInfo = Unsafe.As<uint, PrimeInfo>(ref primeRef);
prime = primeInfo.Prime;
magic = primeInfo.Magic;
shift = primeInfo.Shift;
return;
}
}

throw new ArgumentException(SR.Arg_HTCapacityOverflow);
}

public static void ExpandPrimeInfo(int oldSize, out int prime, out uint magic, out int shift)
{
int newSize = 2 * oldSize;
// Allow the hashtables to grow to maximum possible size (~2G elements) before encoutering capacity overflow.
// Note that this check works even when _items.Length overflowed thanks to the (uint) cast
if ((uint)newSize > MaxPrimeArrayLength && MaxPrimeArrayLength > oldSize)
{
Debug.Assert(MaxPrimeArrayLength == GetPrime(MaxPrimeArrayLength), "Invalid MaxPrimeArrayLength");

PrimeInfo primeInfo = Unsafe.As<uint, PrimeInfo>(ref PrimeMagicShift[PrimeMagicShift.Length - 3]);
prime = primeInfo.Prime;
magic = primeInfo.Magic;
shift = primeInfo.Shift;
return;
}

NearestPrimeInfo(newSize, out prime, out magic, out shift);
}
}
}

0 comments on commit a8917c0

Please sign in to comment.