diff --git a/README.md b/README.md index 7ab6839..2983c11 100644 --- a/README.md +++ b/README.md @@ -228,26 +228,25 @@ BenchmarkDotNet v0.13.12, Windows 11 (10.0.22631.3880/23H2/2023Update/SunValley3 | RobinhoodMap | 1000000 | 84.237352 ms | 1.6354160 ms | 2.2926251 ms | 123 B | | Dictionary | 1000000 | 45.689701 ms | 0.9896159 ms | 2.8867563 ms | 67 B | -### Create StringWrapperBenchmark (cached hashcode) - -| Method | Length | Mean | Error | StdDev | Allocated | -|--------------|---------|----------------|---------------|---------------|-----------| -| DenseMap | 1000 | 0.005590 ms | 0.0000622 ms | 0.0000582 ms | - | -| RobinhoodMap | 1000 | 0.004822 ms | 0.0000862 ms | 0.0000807 ms | - | -| Dictionary | 1000 | 0.006721 ms | 0.0001277 ms | 0.0001311 ms | - | -| DenseMap | 10000 | 0.072046 ms | 0.0005074 ms | 0.0004237 ms | - | -| RobinhoodMap | 10000 | 0.071678 ms | 0.0010047 ms | 0.0008390 ms | - | -| Dictionary | 10000 | 0.134088 ms | 0.0004288 ms | 0.0004011 ms | - | -| DenseMap | 100000 | 1.111280 ms | 0.0174223 ms | 0.0154444 ms | 1 B | -| RobinhoodMap | 100000 | 1.359501 ms | 0.0153216 ms | 0.0143318 ms | 1 B | -| Dictionary | 100000 | 1.866555 ms | 0.0053967 ms | 0.0045064 ms | 1 B | -| DenseMap | 400000 | 13.668025 ms | 0.0525273 ms | 0.0465641 ms | 12 B | -| RobinhoodMap | 400000 | 13.020727 ms | 0.0614468 ms | 0.0479736 ms | 12 B | -| Dictionary | 400000 | 10.846306 ms | 0.0903438 ms | 0.0800874 ms | 12 B | -| DenseMap | 900000 | 34.296921 ms | 0.1181645 ms | 0.0986727 ms | 49 B | -| RobinhoodMap | 900000 | 33.615793 ms | 0.4644336 ms | 0.4344315 ms | 49 B | -| Dictionary | 900000 | 41.753783 ms | 0.8050352 ms | 0.7906522 ms | 4 B | -| DenseMap | 1000000 | 40.145635 ms | 0.7456945 ms | 0.6975231 ms | 57 B | -| RobinhoodMap | 1000000 | 41.060782 ms | 0.5800290 ms | 0.5141806 ms | 5 B | -| Dictionary | 1000000 | 40.949160 ms | 0.3808110 ms | 0.3179945 ms | 5 B | - +### Get string benchmark using XXHash3StringHasher + +| Method | Length | Mean | Error | StdDev | Allocated | +|------------- |-------- |-------------:|-------------:|-------------:|----------:| +| DenseMap | 1000 | 0.0061 ms | 0.000052 ms | 0.000044 ms | - | +| RobinhoodMap | 1000 | 0.0082 ms | 0.000113 ms | 0.000106 ms | - | +| Dictionary | 1000 | 0.0075 ms | 0.000057 ms | 0.000047 ms | - | +| DenseMap | 10000 | 0.0655 ms | 0.000178 ms | 0.000158 ms | - | +| RobinhoodMap | 10000 | 0.1136 ms | 0.001130 ms | 0.001057 ms | - | +| Dictionary | 10000 | 0.1455 ms | 0.000811 ms | 0.000677 ms | - | +| DenseMap | 100000 | 0.9943 ms | 0.0176 ms | 0.0165 ms | 1 B | +| RobinhoodMap | 100000 | 1.8320 ms | 0.0049 ms | 0.0046 ms | 1 B | +| Dictionary | 100000 | 1.8777 ms | 0.0034 ms | 0.0032 ms | 3 B | +| DenseMap | 400000 | 10.3705 ms | 0.0636 ms | 0.0563 ms | 12 B | +| RobinhoodMap | 400000 | 17.7692 ms | 0.1609 ms | 0.1426 ms | 23 B | +| Dictionary | 400000 | 9.3516 ms | 0.1836 ms | 0.1964 ms | 12 B | +| DenseMap | 900000 | 32.7895 ms | 0.5783 ms | 0.5410 ms | 46 B | +| RobinhoodMap | 900000 | 56.5273 ms | 0.6319 ms | 0.5602 ms | 82 B | +| Dictionary | 900000 | 42.8022 ms | 1.2363 ms | 3.5670 ms | 49 B | +| DenseMap | 1000000 | 35.2224 ms | 0.5863 ms | 0.5484 ms | 57 B | +| RobinhoodMap | 1000000 | 68.3534 ms | 1.1398 ms | 1.0661 ms | 92 B | +| Dictionary | 1000000 | 42.9991 ms | 0.4173 ms | 0.3699 ms | 5 B | diff --git a/benchmarks/Faster.Map.Benchmark/AddBenchmark.cs b/benchmarks/Faster.Map.Benchmark/AddBenchmark.cs index 95c68fa..33fec47 100644 --- a/benchmarks/Faster.Map.Benchmark/AddBenchmark.cs +++ b/benchmarks/Faster.Map.Benchmark/AddBenchmark.cs @@ -26,7 +26,7 @@ public class AddBenchmark #region Properties - [Params(1000, 10000, 100000, 400000, 900000, 1000000)] + [Params(10000, 100000, 400000, 800000, 900000)] public uint Length { get; set; } #endregion @@ -52,7 +52,7 @@ public void Add() public void Setup() { // round of length to power of 2 prevent resizing - uint length = BitOperations.RoundUpToPowerOf2(Length) * 2; + uint length = BitOperations.RoundUpToPowerOf2(Length); int dicLength = HashHelpers.GetPrime((int)Length); _dense = new DenseMap(length); @@ -65,26 +65,29 @@ public void Setup() [Benchmark] public void DenseMap() { - foreach (var key in keys.Take((int)Length)) + for (int i = 0; i < Length; i++) { + var key = keys[i]; _dense.Emplace(key, key); - } + } } [Benchmark] public void RobinhoodMap() { - foreach (var key in keys.Take((int)Length)) + for (int i = 0; i < Length; i++) { + var key = keys[i]; _robinhoodMap.Emplace(key, key); } - } + } [Benchmark] public void Dictionary() { - foreach (var key in keys.Take((int)Length)) + for (int i = 0; i < Length; i++) { + var key = keys[i]; dic.Add(key, key); } } diff --git a/benchmarks/Faster.Map.Benchmark/GetBenchmark.cs b/benchmarks/Faster.Map.Benchmark/GetBenchmark.cs index 388b40e..c1a5264 100644 --- a/benchmarks/Faster.Map.Benchmark/GetBenchmark.cs +++ b/benchmarks/Faster.Map.Benchmark/GetBenchmark.cs @@ -72,24 +72,24 @@ public void DenseMap() } } - [Benchmark] - public void RobinhoodMap() - { - for (int i = 0; i < Length; ++i) - { - var key = keys[i]; - _robinHoodMap.Get(key, out var _); - } - } - - [Benchmark(Baseline = true)] - public void Dictionary() - { - for (int i = 0; i < Length; ++i) - { - var key = keys[i]; - _dictionary.TryGetValue(key, out var _); - } - } + //[Benchmark] + //public void RobinhoodMap() + //{ + // for (int i = 0; i < Length; ++i) + // { + // var key = keys[i]; + // _robinHoodMap.Get(key, out var _); + // } + //} + + //[Benchmark(Baseline = true)] + //public void Dictionary() + //{ + // for (int i = 0; i < Length; ++i) + // { + // var key = keys[i]; + // _dictionary.TryGetValue(key, out var _); + // } + //} } } \ No newline at end of file diff --git a/benchmarks/Faster.Map.Benchmark/StringWrapperBenchmark.cs b/benchmarks/Faster.Map.Benchmark/IntBenchmark.cs similarity index 62% rename from benchmarks/Faster.Map.Benchmark/StringWrapperBenchmark.cs rename to benchmarks/Faster.Map.Benchmark/IntBenchmark.cs index 44ae605..70a067e 100644 --- a/benchmarks/Faster.Map.Benchmark/StringWrapperBenchmark.cs +++ b/benchmarks/Faster.Map.Benchmark/IntBenchmark.cs @@ -1,31 +1,30 @@ using BenchmarkDotNet.Attributes; -using Faster.Map.Core; +using Faster.Map.Hasher; using System; using System.Collections; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Numerics; namespace Faster.Map.Benchmark { [MarkdownExporterAttribute.GitHub] [MemoryDiagnoser] - public class StringWrapperBenchmark + public class IntBenchmark { #region Fields - private DenseMap _denseMap; - private Dictionary _dictionary; - private RobinhoodMap _robinhoodMap; + private DenseMap _denseMap; + private Dictionary _dictionary; + private RobinhoodMap _robinhoodMap; - private string[] keys; + private uint[] keys; #endregion #region Properties - [Params(/*1000, 10000, 100000, 400000, 900000,*/ 1000000)] + [Params(/*10000, 100000, 400000, 900000,*/ 1000000)] public uint Length { get; set; } #endregion @@ -39,20 +38,20 @@ public void Setup() var output = File.ReadAllText("Numbers.txt"); var splittedOutput = output.Split(','); - keys = new string[Length]; + keys = new uint[Length]; for (var index = 0; index < Length; index++) { - keys[index] = splittedOutput[index]; + keys[index] = Convert.ToUInt32(splittedOutput[index]); } // round of length to power of 2 prevent resizing - uint length = BitOperations.RoundUpToPowerOf2(Length) * 2; + uint length = BitOperations.RoundUpToPowerOf2(Length); int dicLength = HashHelpers.GetPrime((int)Length); - _denseMap = new DenseMap(length); - _dictionary = new Dictionary(dicLength); - _robinhoodMap = new RobinhoodMap(length); + _denseMap = new DenseMap(length, 0.875, new DefaultHasher()); + _dictionary = new Dictionary(dicLength); + _robinhoodMap = new RobinhoodMap(length); foreach (var key in keys) { @@ -80,14 +79,14 @@ public void DenseMap() // } //} - //[Benchmark] - //public void Dictionary() - //{ - // foreach (var key in keys) - // { - // _dictionary.TryGetValue(key, out var result); - // } - //} + [Benchmark] + public void Dictionary() + { + foreach (var key in keys) + { + _dictionary.TryGetValue(key, out var result); + } + } } } diff --git a/benchmarks/Faster.Map.Benchmark/Program.cs b/benchmarks/Faster.Map.Benchmark/Program.cs index 73ea707..74960e7 100644 --- a/benchmarks/Faster.Map.Benchmark/Program.cs +++ b/benchmarks/Faster.Map.Benchmark/Program.cs @@ -7,7 +7,7 @@ class Program { static void Main(string[] args) { - BenchmarkRunner.Run(); + BenchmarkRunner.Run(new DebugInProcessConfig()); //BenchmarkRunner.Run(); //BenchmarkRunner.Run(); //BenchmarkRunner.Run(); diff --git a/benchmarks/Faster.Map.Benchmark/StringBenchmark.cs b/benchmarks/Faster.Map.Benchmark/StringBenchmark.cs index b9cec83..ec9a533 100644 --- a/benchmarks/Faster.Map.Benchmark/StringBenchmark.cs +++ b/benchmarks/Faster.Map.Benchmark/StringBenchmark.cs @@ -1,5 +1,4 @@ using BenchmarkDotNet.Attributes; -using Faster.Map.Core; using Faster.Map.Hasher; using System; using System.Collections; @@ -26,7 +25,7 @@ public class StringBenchmark #region Properties - [Params(/*1000, 10000, 100000, 400000, 900000,*/ 1000000)] + [Params(10000, 100000, 400000, 900000, 1000000)] public uint Length { get; set; } #endregion @@ -48,7 +47,7 @@ public void Setup() } // round of length to power of 2 prevent resizing - uint length = BitOperations.RoundUpToPowerOf2(Length) * 2; + uint length = BitOperations.RoundUpToPowerOf2(Length); int dicLength = HashHelpers.GetPrime((int)Length); _denseMap = new DenseMap(length, 0.875, new XxHash3StringHasher()); diff --git a/src/Contracts/IHasher.cs b/src/Contracts/IHasher.cs index e132698..c0f5f2d 100644 --- a/src/Contracts/IHasher.cs +++ b/src/Contracts/IHasher.cs @@ -2,6 +2,6 @@ { public interface IHasher { - uint ComputeHash(TKey key); + ulong ComputeHash(TKey key); } } diff --git a/src/Core/StringWrapper.cs b/src/Core/StringWrapper.cs deleted file mode 100644 index d87f1ad..0000000 --- a/src/Core/StringWrapper.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 2024, Wiljan Ruizendaal. All rights reserved. -// Distributed under the MIT Software License, Version 1.0. - -using System; -using System.IO.Hashing; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace Faster.Map.Core -{ - - public record struct StringWrapper: IEquatable - { - int hashcode; - public string Unwrapped { get; set; } - - public StringWrapper(string unwrapped) - { - Unwrapped = unwrapped; - - var span = unwrapped.AsSpan(); - var result = XxHash3.HashToUInt64(MemoryMarshal.AsBytes(span)) >> 32; - hashcode = Unsafe.As(ref result); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() - { - return hashcode; - } - - public static implicit operator string(StringWrapper source) - { - return source.Unwrapped; - } - - public static implicit operator StringWrapper(string source) - { - return new StringWrapper(source); - } - - } -} diff --git a/src/DenseMap.cs b/src/DenseMap.cs index c19532d..f21e015 100644 --- a/src/DenseMap.cs +++ b/src/DenseMap.cs @@ -297,7 +297,7 @@ public void Emplace(TKey key, TValue value) var target = Vector128.Create(h2); // This operation ensures that `index` is in the range [0, capacity - 1] by using only the lower bits of `hashcode`, // which helps in efficient and quick indexing. - uint index = hashcode & _lengthMinusOne; + var index = hashcode & _lengthMinusOne; // Initialize the probing jump distance to zero, which will increase with each probe iteration. uint jumpDistance = 0; @@ -394,7 +394,7 @@ public bool Get(TKey key, out TValue value) var target = Vector128.Create(h2); // This operation ensures that `index` is in the range [0, capacity - 1] by using only the lower bits of `hashcode`, // which helps in efficient and quick indexing. - uint index = hashcode & _lengthMinusOne; + var index = hashcode & _lengthMinusOne; // Initialize a variable to keep track of the distance to jump when probing the map. uint jumpDistance = 0; @@ -480,7 +480,7 @@ public ref TValue GetValueRefOrAddDefault(TKey key) var target = Vector128.Create(h2); // This operation ensures that `index` is in the range [0, capacity - 1] by using only the lower bits of `hashcode`, // which helps in efficient and quick indexing. - uint index = hashcode & _lengthMinusOne; + var index = hashcode & _lengthMinusOne; // Initialize the probing jump distance to zero, which will increase with each probe iteration. uint jumpDistance = 0; @@ -569,7 +569,7 @@ public bool Update(TKey key, TValue value) var target = Vector128.Create(h2); // This operation ensures that `index` is in the range [0, capacity - 1] by using only the lower bits of `hashcode`, // which helps in efficient and quick indexing. - uint index = hashcode & _lengthMinusOne; + var index = hashcode & _lengthMinusOne; // Initialize `jumpDistance` to control the distance between probes, starting at zero. uint jumpDistance = 0; @@ -652,7 +652,7 @@ public bool Remove(TKey key) var target = Vector128.Create(h2); // This operation ensures that `index` is in the range [0, capacity - 1] by using only the lower bits of `hashcode`, // which helps in efficient and quick indexing. - uint index = hashcode & _lengthMinusOne; + var index = hashcode & _lengthMinusOne; // Initialize `jumpDistance` to control the distance between probes, starting at zero. uint jumpDistance = 0; @@ -756,7 +756,7 @@ public bool Contains(TKey key) var target = Vector128.Create(h2); // This operation ensures that `index` is in the range [0, capacity - 1] by using only the lower bits of `hashcode`, // which helps in efficient and quick indexing. - uint index = hashcode & _lengthMinusOne; + var index = hashcode & _lengthMinusOne; // Initialize `jumpDistance` to control the distance between probes, starting at zero. uint jumpDistance = 0; @@ -918,7 +918,7 @@ private void Resize() var entry = Find(oldEntries, i); var hashcode = _hasher.ComputeHash(entry.Key); - uint index = hashcode & _lengthMinusOne; + var index = hashcode & _lengthMinusOne; uint jumpDistance = 0; while (true) @@ -949,10 +949,10 @@ private void Resize() /// The index to look up. /// A reference to the found element. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ref T Find(T[] array, uint index) + private static ref T Find(T[] array, ulong index) { ref var arr0 = ref MemoryMarshal.GetArrayDataReference(array); - return ref Unsafe.Add(ref arr0, index); + return ref Unsafe.Add(ref arr0, Unsafe.As(ref index)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -968,7 +968,7 @@ private static ref T Find(T[] array, int index) /// The hashcode. /// The 7 lowest bits of the hashcode. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static sbyte H2(uint hashcode) => (sbyte)(hashcode >> 25); + private static sbyte H2(ulong hashcode) => (sbyte)(hashcode >> 57); /// /// Resets the lowest significant bit in the given value. diff --git a/src/Faster.Map.csproj b/src/Faster.Map.csproj index 4672e78..249e17c 100644 --- a/src/Faster.Map.csproj +++ b/src/Faster.Map.csproj @@ -50,7 +50,7 @@ - + diff --git a/src/Hasher/DefaultHasher.cs b/src/Hasher/DefaultHasher.cs index 257ad32..c6887c3 100644 --- a/src/Hasher/DefaultHasher.cs +++ b/src/Hasher/DefaultHasher.cs @@ -5,7 +5,9 @@ namespace Faster.Map.Hasher { public class DefaultHasher : IHasher { + ulong _goldenRatio = 11400714819323198485; + [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint ComputeHash(TKey key) => (uint)key.GetHashCode(); + public ulong ComputeHash(TKey key) => (uint)key.GetHashCode() * _goldenRatio; } } diff --git a/src/Hasher/XxHash3Hasher.cs b/src/Hasher/XxHash3Hasher.cs index 8885222..89ae9a8 100644 --- a/src/Hasher/XxHash3Hasher.cs +++ b/src/Hasher/XxHash3Hasher.cs @@ -8,11 +8,10 @@ namespace Faster.Map.Hasher public class XxHash3Hasher : IHasher where T : unmanaged { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint ComputeHash(T key) + public ulong ComputeHash(T key) { - var span = MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(key), 1)); - var result = XxHash3.HashToUInt64(MemoryMarshal.AsBytes(span)) >> 32; - return Unsafe.As(ref result); + var span = MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in key), 1)); + return XxHash3.HashToUInt64(span); } } } diff --git a/src/Hasher/XxHash3StringHasher.cs b/src/Hasher/XxHash3StringHasher.cs index 34d941f..9df2e88 100644 --- a/src/Hasher/XxHash3StringHasher.cs +++ b/src/Hasher/XxHash3StringHasher.cs @@ -9,10 +9,9 @@ namespace Faster.Map.Hasher public class XxHash3StringHasher : IHasher { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint ComputeHash(string key) - { - var result = XxHash3.HashToUInt64(MemoryMarshal.AsBytes(key.AsSpan())) >> 32; - return Unsafe.As(ref result); + public ulong ComputeHash(string key) + { + return XxHash3.HashToUInt64(MemoryMarshal.AsBytes(key.AsSpan())); } } } diff --git a/unittests/Faster.Map.DenseMap.Tests/DenseMapStringWrapperTests.cs b/unittests/Faster.Map.DenseMap.Tests/DenseMapStringWrapperTests.cs deleted file mode 100644 index 8801332..0000000 --- a/unittests/Faster.Map.DenseMap.Tests/DenseMapStringWrapperTests.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Faster.Map.Core; -using Xunit; - -namespace Faster.Map.DenseMap.Tests -{ - public class DenseMapStringWrapperTests - { - - [Fact] - public void Emplace_StringWrapper_Should_Return_Correct_String() - { - // Assign - var map = new DenseMap(); - - map.Emplace("one", "Nine"); - - // Act - map.Get("one", out var result); ; - - // Assert - Assert.Equal("Nine", result); - } - - [Theory] - [InlineData(1)] - [InlineData(10)] - [InlineData(100)] - [InlineData(1000)] - [InlineData(10000)] - [InlineData(100000)] - [InlineData(1000000)] - public void Get_StringsWrapper_Return_Succesful(uint amount) - { - // Arrange - var map = new DenseMap(); - - for (uint i = 0; i < amount; i++) - { - map.Emplace(i.ToString(), System.Guid.NewGuid().ToString()); - } - - for (uint i = 0; i < amount; i++) - { - // Act - var result = map.Get(i.ToString(), out var _); - if (!result) - { - // Assert - Assert.Fail(); - } - } - } - - } -} diff --git a/unittests/Faster.Map.DenseMap.Tests/IntegerHasherTests.cs b/unittests/Faster.Map.DenseMap.Tests/IntegerHasherTests.cs index 933dde3..4689025 100644 --- a/unittests/Faster.Map.DenseMap.Tests/IntegerHasherTests.cs +++ b/unittests/Faster.Map.DenseMap.Tests/IntegerHasherTests.cs @@ -1,7 +1,5 @@ using System; using Xunit; -using Faster.Map; -using Faster.Map.Core; using System.Collections.Generic; using Faster.Map.Hasher; diff --git a/unittests/Faster.Map.DenseMap.Tests/StringHasherTests.cs b/unittests/Faster.Map.DenseMap.Tests/StringHasherTests.cs index 8818455..ed01528 100644 --- a/unittests/Faster.Map.DenseMap.Tests/StringHasherTests.cs +++ b/unittests/Faster.Map.DenseMap.Tests/StringHasherTests.cs @@ -1,7 +1,5 @@ using System; using Xunit; -using Faster.Map; -using Faster.Map.Core; using System.Collections.Generic; using Faster.Map.Hasher;