From 3b0fe7a046598170da4b6b58fae8fcd1ae4fe91d Mon Sep 17 00:00:00 2001 From: hadashiA Date: Mon, 18 Sep 2023 18:13:38 +0900 Subject: [PATCH] Add ankerl::unordered_dense inspied hashtable --- .../Runtime/Internal/TypeKeyHashTable2.cs | 146 ++++++++++++++++++ .../Internal/TypeKeyHashTable2.cs.meta | 3 + .../Assets/VContainer/Runtime/Registry.cs | 6 +- 3 files changed, 152 insertions(+), 3 deletions(-) create mode 100644 VContainer/Assets/VContainer/Runtime/Internal/TypeKeyHashTable2.cs create mode 100644 VContainer/Assets/VContainer/Runtime/Internal/TypeKeyHashTable2.cs.meta diff --git a/VContainer/Assets/VContainer/Runtime/Internal/TypeKeyHashTable2.cs b/VContainer/Assets/VContainer/Runtime/Internal/TypeKeyHashTable2.cs new file mode 100644 index 00000000..975d875a --- /dev/null +++ b/VContainer/Assets/VContainer/Runtime/Internal/TypeKeyHashTable2.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace VContainer.Internal +{ + sealed class TypeKeyHashTable2 + { + struct Bucket + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint DistAndFingerPrintFromHash(int hash) + { + return DistOne | ((uint)hash & FingerPrintMask); + } + + public const uint DistOne = 0x00000100; + public const uint FingerPrintMask = 0x000000FF; + + /// + /// upper 3 bytes: dist (distance of , also known PSL (probe sequence length)) + /// lower 1 bytes: fingerprint (lower 1 byte of hash code) + /// + public uint DistAndFingerPrint; + + /// + /// The index that point to the location where value is actually stored. + /// + public int EntryIndex; + } + + readonly Bucket[] buckets; + readonly KeyValuePair[] entries; + readonly int indexFor; + + int insertedEntryLength; + + public TypeKeyHashTable2(KeyValuePair[] values, float loadFactor = 0.75f) + { + var initialCapacity = (int)(values.Length / loadFactor); + + // make power of 2(and use mask) + // see: Hashing https://en.wikipedia.org/wiki/Hash_table + var capacity = 1; + while (capacity < initialCapacity) + { + capacity <<= 1; + } + + buckets = new Bucket[capacity]; + entries = new KeyValuePair[values.Length]; + indexFor = buckets.Length - 1; + + var entryIndex = 0; + foreach (var x in values) + { + Insert(x, entryIndex++); + } + } + + public bool TryGet(Type key, out TValue value) + { + var hash = RuntimeHelpers.GetHashCode(key); + var distAndFingerPrint = Bucket.DistAndFingerPrintFromHash(hash); + var bucketIndex = hash & indexFor; + var bucket = buckets[bucketIndex]; + + while (true) + { + if (distAndFingerPrint == bucket.DistAndFingerPrint) + { + // compare key + var entry = entries[bucket.EntryIndex]; + if (key == entry.Key) + { + value = entry.Value; + return true; + } + } + else if (distAndFingerPrint > bucket.DistAndFingerPrint) + { + // not found + value = default; + return false; + } + distAndFingerPrint += Bucket.DistOne; + bucketIndex = NextBucketIndex(bucketIndex); + bucket = buckets[bucketIndex]; + } + + } + + void Insert(KeyValuePair entry, int entryIndex) + { + var hash = RuntimeHelpers.GetHashCode(entry.Key); + var distAndFingerPrint = Bucket.DistAndFingerPrintFromHash(hash); + var bucketIndex = hash & indexFor; + + // robin food hashing + while (distAndFingerPrint <= buckets[bucketIndex].DistAndFingerPrint) + { + // key already exists + if (distAndFingerPrint == buckets[bucketIndex].DistAndFingerPrint && + entry.Key == entries[buckets[bucketIndex].EntryIndex].Key) + { + throw new InvalidOperationException($"The key already exists: {entry.Key}"); + } + + // + bucketIndex = NextBucketIndex(bucketIndex); + distAndFingerPrint += Bucket.DistOne; // + } + + entries[entryIndex] = entry; + SetBucketAt(bucketIndex, new Bucket + { + DistAndFingerPrint = distAndFingerPrint, + EntryIndex = entryIndex + }); + } + + /// + /// + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void SetBucketAt(int i, Bucket bucket) + { + while (buckets[i].DistAndFingerPrint != 0) + { + // swap + (buckets[i], bucket) = (bucket, buckets[i]); + bucket.DistAndFingerPrint += Bucket.DistOne; + i = NextBucketIndex(i); + } + buckets[i] = bucket; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + int NextBucketIndex(int i) + { + return i + 1 >= buckets.Length ? 0 : i + 1; + } + } +} \ No newline at end of file diff --git a/VContainer/Assets/VContainer/Runtime/Internal/TypeKeyHashTable2.cs.meta b/VContainer/Assets/VContainer/Runtime/Internal/TypeKeyHashTable2.cs.meta new file mode 100644 index 00000000..8794f0d0 --- /dev/null +++ b/VContainer/Assets/VContainer/Runtime/Internal/TypeKeyHashTable2.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9c311a30613d4728b2b891e770db92f7 +timeCreated: 1695016383 \ No newline at end of file diff --git a/VContainer/Assets/VContainer/Runtime/Registry.cs b/VContainer/Assets/VContainer/Runtime/Registry.cs index 4e42e418..9f8b7bc1 100644 --- a/VContainer/Assets/VContainer/Runtime/Registry.cs +++ b/VContainer/Assets/VContainer/Runtime/Registry.cs @@ -9,7 +9,7 @@ public sealed class Registry [ThreadStatic] static IDictionary buildBuffer = new Dictionary(128); - readonly FixedTypeKeyHashtable hashTable; + readonly TypeKeyHashTable2 hashTable; public static Registry Build(Registration[] registrations) { @@ -40,7 +40,7 @@ public static Registry Build(Registration[] registrations) } } - var hashTable = new FixedTypeKeyHashtable(buildBuffer.ToArray()); + var hashTable = new TypeKeyHashTable2(buildBuffer.ToArray()); return new Registry(hashTable); } @@ -95,7 +95,7 @@ static void AddCollectionToBuildBuffer(IDictionary buf, Regi } } - Registry(FixedTypeKeyHashtable hashTable) + Registry(TypeKeyHashTable2 hashTable) { this.hashTable = hashTable; }