diff --git a/Benchmark/BenchmarkAHash.cs b/Benchmark/BenchmarkAHash.cs
index ba9caa9..6702acd 100644
--- a/Benchmark/BenchmarkAHash.cs
+++ b/Benchmark/BenchmarkAHash.cs
@@ -1,6 +1,9 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Diagnostics.Windows.Configs;
using BetterCollections.Cryptography;
+#if NET8_0_OR_GREATER
+using BetterCollections.Cryptography.AHasherImpl;
+#endif
namespace Benchmark;
@@ -9,7 +12,7 @@ namespace Benchmark;
[DisassemblyDiagnoser]
public class BenchmarkAHash
{
- private readonly AHasher AHasher = new();
+ private readonly AHasherLegacy aHasherLegacy = new();
[Benchmark]
public ulong AHash()
@@ -17,11 +20,37 @@ public ulong AHash()
var r = 0ul;
for (int i = 0; i < 1000; i++)
{
- r += AHasher.Hash((ulong)i);
+ r += aHasherLegacy.Hash((ulong)i);
}
return r;
}
+#if NET8_0_OR_GREATER
+ [Benchmark]
+ public int AHash2()
+ {
+ var r = 0;
+ for (int i = 0; i < 1000; i++)
+ {
+ r += AHasher.Combine(i);
+ }
+ return r;
+ }
+
+ [Benchmark]
+ public int AHash3()
+ {
+ var r = 0;
+ for (int i = 0; i < 1000; i++)
+ {
+ var hasher = AesHasher.Create(AHasher.GlobalRandomState);
+ hasher = AesHasher.Add(hasher, i);
+ r += AesHasher.ToHashCode(hasher);
+ }
+ return r;
+ }
+#endif
+
[Benchmark(Baseline = true)]
public int HashCodeCombine()
{
diff --git a/Benchmark/BenchmarkAHashString.cs b/Benchmark/BenchmarkAHashString.cs
new file mode 100644
index 0000000..bed4c35
--- /dev/null
+++ b/Benchmark/BenchmarkAHashString.cs
@@ -0,0 +1,58 @@
+using System.Runtime.InteropServices;
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Diagnostics.Windows.Configs;
+using BetterCollections.Cryptography;
+#if NET8_0_OR_GREATER
+using BetterCollections.Cryptography.AHasherImpl;
+#endif
+
+namespace Benchmark;
+
+[MemoryDiagnoser]
+[JitStatsDiagnoser]
+[DisassemblyDiagnoser]
+public class BenchmarkAHashString
+{
+ [Params("asd", "1234567890", "1234567890_1234567890_1234567890_1234567890",
+ "1234567890_1234567890_1234567890_1234567890_1234567890_1234567890_1234567890_1234567890")]
+ public string Str;
+
+#if NET8_0_OR_GREATER
+ [Benchmark]
+ public int AHash_AddString_1()
+ {
+ var hasher = AHasher.Global;
+ hasher.AddString(Str);
+ return hasher.ToHashCode();
+ }
+
+ [Benchmark]
+ public int AHash_AddString_2()
+ {
+ var hasher = AesHasher.Create(AHasher.GlobalRandomState);
+ hasher = AesHasher.AddString(hasher, Str);
+ return AesHasher.ToHashCode(hasher);
+ }
+
+ [Benchmark]
+ public int Soft_AddString1()
+ {
+ var r = 0;
+ for (int i = 0; i < 1000; i++)
+ {
+ var hasher = SoftHasher.Create(AHasher.GlobalRandomState);
+ hasher = SoftHasher.AddString(hasher, Str);
+ r += SoftHasher.ToHashCode(hasher);
+ }
+ return r;
+ }
+#endif
+
+ [Benchmark(Baseline = true)]
+ public int HashCode_AddBytes()
+ {
+ var hasher = new HashCode();
+ hasher.AddBytes(MemoryMarshal.AsBytes(Str.AsSpan()));
+ return hasher.ToHashCode();
+ }
+}
diff --git a/BetterCollections/Cryptography/AHasher/AHasher.cs b/BetterCollections/Cryptography/AHasher/AHasher.cs
new file mode 100644
index 0000000..2b1e04e
--- /dev/null
+++ b/BetterCollections/Cryptography/AHasher/AHasher.cs
@@ -0,0 +1,216 @@
+#if NET8_0_OR_GREATER
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using BetterCollections.Cryptography.AHasherImpl;
+
+namespace BetterCollections.Cryptography;
+
+// reference https://github.com/tkaitchuck/aHash/tree/master
+
+///
+/// A hasher that ensures even distribution of each bit
+/// If possible use Aes SIMD acceleration (.net7+)
+///
+public struct AHasher : IHasher
+{
+ #region GlobalRandomState
+
+ public static AHasherRandomState GlobalRandomState
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ get => _globalRandomState;
+ }
+
+ public static readonly AHasherRandomState _globalRandomState = GenerateRandomState();
+
+ [ThreadStatic]
+ private static AHasherRandomState _threadCurrentRandomState;
+ [ThreadStatic]
+ private static bool _threadCurrentHas;
+
+ public static AHasherRandomState ThreadCurrentRandomState
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ get
+ {
+ if (!_threadCurrentHas)
+ {
+ _threadCurrentRandomState = GenerateRandomState();
+ _threadCurrentHas = true;
+ }
+ return _threadCurrentRandomState;
+ }
+ }
+
+ #endregion
+
+ #region RandomState
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static AHasherRandomState GenerateRandomState()
+ {
+ var rand = Random.Shared;
+ Unsafe.SkipInit(out AHasherRandomState state);
+ rand.NextBytes(MemoryMarshal.Cast(state.UnsafeAsMutableSpan()));
+ return state;
+ }
+
+ #endregion
+
+
+ #region Create
+
+ public static AHasher Global
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ get;
+ } = new(GlobalRandomState);
+
+ public static AHasher ThreadCurrent
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ get;
+ } = new(ThreadCurrentRandomState);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static AHasher CreateRandom() => new(GenerateRandomState());
+
+ #endregion
+
+ #region Impl
+
+ private AHasher2Data data;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public AHasher(AHasherRandomState randomState)
+ {
+ Unsafe.SkipInit(out this);
+ data = AesHasher.IsSupported ? AesHasher.Create(randomState) : SoftHasher.Create(randomState);
+ }
+
+ #region IHasher
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public void Add(int value)
+ {
+ data = AesHasher.IsSupported ? AesHasher.Add(data, value) : SoftHasher.Add(data, value);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public void Add(long value)
+ {
+ data = AesHasher.IsSupported ? AesHasher.Add(data, value) : SoftHasher.Add(data, value);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public void Add(T value)
+ {
+ data = AesHasher.IsSupported ? AesHasher.Add(data, value) : SoftHasher.Add(data, value);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public void Add(T value, IEqualityComparer? comparer)
+ {
+ data = AesHasher.IsSupported ? AesHasher.Add(data, value, comparer) : SoftHasher.Add(data, value, comparer);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public void AddBytes(ReadOnlySpan value)
+ {
+ data = AesHasher.IsSupported ? AesHasher.AddBytes(data, value) : SoftHasher.AddBytes(data, value);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public void AddString(ReadOnlySpan value)
+ {
+ data = AesHasher.IsSupported ? AesHasher.AddString(data, value) : SoftHasher.AddString(data, value);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public void AddString(ReadOnlySpan value)
+ {
+ data = AesHasher.IsSupported ? AesHasher.AddString(data, value) : SoftHasher.AddString(data, value);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public int ToHashCode() =>
+ AesHasher.IsSupported ? AesHasher.ToHashCode(data) : SoftHasher.ToHashCode(data);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public long ToHashCodeLong() =>
+ AesHasher.IsSupported ? AesHasher.ToHashCodeLong(data) : SoftHasher.ToHashCodeLong(data);
+
+ #endregion
+
+ #endregion
+
+ #region Combine
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static int Combine(T1 value1)
+ {
+ return AesHasher.IsSupported ? AesHasher.Combine(value1) : SoftHasher.Combine(value1);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static int Combine(T1 value1, T2 value2)
+ {
+ return AesHasher.IsSupported ? AesHasher.Combine(value1, value2) : SoftHasher.Combine(value1, value2);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static int Combine(T1 value1, T2 value2, T3 value3)
+ {
+ return AesHasher.IsSupported
+ ? AesHasher.Combine(value1, value2, value3)
+ : SoftHasher.Combine(value1, value2, value3);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4)
+ {
+ return AesHasher.IsSupported
+ ? AesHasher.Combine(value1, value2, value3, value4)
+ : SoftHasher.Combine(value1, value2, value3, value4);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5)
+ {
+ return AesHasher.IsSupported
+ ? AesHasher.Combine(value1, value2, value3, value4, value5)
+ : SoftHasher.Combine(value1, value2, value3, value4, value5);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6)
+ {
+ return AesHasher.IsSupported
+ ? AesHasher.Combine(value1, value2, value3, value4, value5, value6)
+ : SoftHasher.Combine(value1, value2, value3, value4, value5, value6);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5,
+ T6 value6, T7 value7)
+ {
+ return AesHasher.IsSupported
+ ? AesHasher.Combine(value1, value2, value3, value4, value5, value6, value7)
+ : SoftHasher.Combine(value1, value2, value3, value4, value5, value6, value7);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5,
+ T6 value6, T7 value7, T8 value8)
+ {
+ return AesHasher.IsSupported
+ ? AesHasher.Combine(value1, value2, value3, value4, value5, value6, value7, value8)
+ : SoftHasher.Combine(value1, value2, value3, value4, value5, value6, value7, value8);
+ }
+
+ #endregion
+}
+
+#endif
diff --git a/BetterCollections/Cryptography/AHasher/AHasher2Data.cs b/BetterCollections/Cryptography/AHasher/AHasher2Data.cs
new file mode 100644
index 0000000..b17b744
--- /dev/null
+++ b/BetterCollections/Cryptography/AHasher/AHasher2Data.cs
@@ -0,0 +1,70 @@
+#if NET8_0_OR_GREATER
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using System.Runtime.Intrinsics;
+
+namespace BetterCollections.Cryptography.AHasherImpl;
+
+public struct AHasher2Data
+{
+ public Vector128 enc;
+ public Vector128 sum;
+ public readonly Vector128 key;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public AHasher2Data(Vector128 enc, Vector128 sum, Vector128 key)
+ {
+ Unsafe.SkipInit(out this);
+ this.enc = enc;
+ this.sum = sum;
+ this.key = key;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public AHasher2Data(Vector128 enc)
+ {
+ Unsafe.SkipInit(out this);
+ this.enc = enc;
+ }
+
+ public ulong buffer
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ get => enc.AsUInt64().GetElement(0);
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ set => enc.AsUInt64().WithElement(0, value);
+ }
+
+ public ulong pad
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ get => enc.AsUInt64().GetElement(1);
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ set => enc.AsUInt64().WithElement(1, value);
+ }
+
+ public Vector128 extra
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ get => sum.AsUInt64();
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ set => sum = value.AsByte();
+ }
+
+ public Vector128 buffer_extra1
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ get => Vector128.ConditionalSelect(Vector128.Create(ulong.MaxValue, 0), enc.AsUInt64(), sum.AsUInt64());
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public AHasher2Data(ulong buffer, ulong pad, Vector128 extra)
+ {
+ Unsafe.SkipInit(out this);
+ this.buffer = buffer;
+ this.pad = pad;
+ this.extra = extra.AsUInt64();
+ }
+}
+
+#endif
diff --git a/BetterCollections/Cryptography/AHasher/AHasherRandomState.cs b/BetterCollections/Cryptography/AHasher/AHasherRandomState.cs
new file mode 100644
index 0000000..fdc1315
--- /dev/null
+++ b/BetterCollections/Cryptography/AHasher/AHasherRandomState.cs
@@ -0,0 +1,67 @@
+#if NET8_0_OR_GREATER
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
+
+namespace BetterCollections.Cryptography;
+
+public readonly struct AHasherRandomState
+{
+ public readonly Vector128 key1;
+ public readonly Vector128 key2;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public AHasherRandomState(Vector128 key1, Vector128 key2)
+ {
+ Unsafe.SkipInit(out this);
+ this.key1 = key1;
+ this.key2 = key2;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public AHasherRandomState(ReadOnlySpan keys)
+ {
+ if (keys.Length < 4) throw new ArgumentOutOfRangeException(nameof(keys), "length of keys must >= 4");
+ Unsafe.SkipInit(out this);
+ key1 = Vector128.LoadUnsafe(in keys[0]).AsByte();
+ key2 = Vector128.LoadUnsafe(in keys[2]).AsByte();
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public AHasherRandomState(ulong a, ulong b)
+ {
+ Unsafe.SkipInit(out this);
+ Unsafe.AsRef(in this.a) = a;
+ Unsafe.AsRef(in this.b) = b;
+ }
+
+ #region Getters
+
+ [UnscopedRef]
+ public ref readonly ulong a
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ get => ref Unsafe.As(ref Unsafe.AsRef(in this));
+ }
+
+ [UnscopedRef]
+ public ref readonly ulong b
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ get => ref Unsafe.Add(ref Unsafe.As(ref Unsafe.AsRef(in this)), 1);
+ }
+
+ #endregion
+
+ [UnscopedRef]
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public ReadOnlySpan AsSpan() => MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in a), 4);
+
+ [UnscopedRef]
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public Span UnsafeAsMutableSpan() => MemoryMarshal.CreateSpan(ref Unsafe.AsRef(in a), 4);
+}
+
+#endif
diff --git a/BetterCollections/Cryptography/AHasher/AesHasher.cs b/BetterCollections/Cryptography/AHasher/AesHasher.cs
new file mode 100644
index 0000000..4b4a489
--- /dev/null
+++ b/BetterCollections/Cryptography/AHasher/AesHasher.cs
@@ -0,0 +1,458 @@
+#if NET8_0_OR_GREATER
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
+using X86 = System.Runtime.Intrinsics.X86;
+using Arm = System.Runtime.Intrinsics.Arm;
+
+namespace BetterCollections.Cryptography.AHasherImpl;
+
+public static class AesHasher
+{
+ public static bool IsSupported
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ get => X86.Aes.IsSupported || Arm.Aes.IsSupported;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static AHasher2Data Create(AHasherRandomState randomState) =>
+ new(randomState.key1, randomState.key2, randomState.key1 ^ randomState.key2);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static AHasher2Data Add(AHasher2Data data, bool value) =>
+ Add(data, Vector128.CreateScalar(value ? (byte)1 : (byte)0));
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static AHasher2Data Add(AHasher2Data data, short value) =>
+ Add(data, Vector128.CreateScalar(value));
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static AHasher2Data Add(AHasher2Data data, ushort value) =>
+ Add(data, Vector128.CreateScalar(value));
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static AHasher2Data Add(AHasher2Data data, int value) =>
+ Add(data, Vector128.CreateScalar(value));
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static AHasher2Data Add(AHasher2Data data, uint value) =>
+ Add(data, Vector128.CreateScalar(value));
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static AHasher2Data Add(AHasher2Data data, long value) =>
+ Add(data, Vector128.CreateScalar(value));
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static AHasher2Data Add(AHasher2Data data, ulong value) =>
+ Add(data, Vector128.CreateScalar(value));
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static AHasher2Data Add(AHasher2Data data, nint value) =>
+ Add(data, Vector128.CreateScalar(value));
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static AHasher2Data Add(AHasher2Data data, nuint value) =>
+ Add(data, Vector128.CreateScalar(value));
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static AHasher2Data Add(AHasher2Data data, float value) =>
+ Add(data, Vector128.CreateScalar(value));
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static AHasher2Data Add(AHasher2Data data, double value) =>
+ Add(data, Vector128.CreateScalar(value));
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static AHasher2Data Add(AHasher2Data data, Int128 value) =>
+ Add(data, Unsafe.As>(ref value));
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static AHasher2Data Add(AHasher2Data data, UInt128 value) =>
+ Add(data, Unsafe.As>(ref value));
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static AHasher2Data Add(AHasher2Data data, decimal value) =>
+ Add(data, Unsafe.As>(ref value));
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization), SkipLocalsInit]
+ public static AHasher2Data Add(AHasher2Data data, T value)
+ {
+ if (Vector128.IsSupported)
+ return Add(data, Vector128.CreateScalar(value).AsByte());
+
+ if (typeof(T) == typeof(Int128) || typeof(T) == typeof(UInt128) || typeof(T) == typeof(Vector128))
+ return Add(data, Unsafe.As>(ref value));
+
+ return Add(data, Vector128.CreateScalar(value?.GetHashCode() ?? 0).AsByte());
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static AHasher2Data Add(AHasher2Data data, T value, IEqualityComparer? comparer)
+ {
+ if (comparer == null) return Add(data, value);
+ else return Add(data, value == null ? 0 : comparer.GetHashCode(value));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static AHasher2Data Add(AHasher2Data data, Vector128 value)
+#if NET7_0
+ where T : struct
+#endif
+ => Add(data, value.AsByte());
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static AHasher2Data Add(AHasher2Data data, Vector128 value)
+ {
+ data.enc = AesDec(data.enc, value);
+ data.sum = ShuffleAndAdd(data.sum, value);
+ return data;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static AHasher2Data AddBytes(AHasher2Data data, ReadOnlySpan value)
+ {
+ data = AddLength(data, value.Length);
+
+ if (value.Length <= 8)
+ {
+ return AddBytes_len_8(data, value);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ static AHasher2Data AddBytes_len_8(AHasher2Data data, ReadOnlySpan value)
+ => Add(data, ReadSmall(value));
+ }
+ else if (value.Length > 32)
+ {
+ if (value.Length > 64)
+ {
+ return AddBytes_len_64(data, value);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ static AHasher2Data AddBytes_len_64(AHasher2Data data, ReadOnlySpan value)
+ {
+ var t0 = Vector128.LoadUnsafe(in value[^(sizeof(ulong) * 2)]);
+ var s0 = AddByU64(data.key, t0);
+ var c0 = AesEnc(data.key, t0);
+ var t2 = Vector128.LoadUnsafe(in value[^(sizeof(ulong) * 6)]);
+ s0 = ShuffleAndAdd(s0, t2);
+ var c2 = AesEnc(data.key, t2);
+ var t1 = Vector128.LoadUnsafe(in value[^(sizeof(ulong) * 4)]);
+ var s1 = AddByU64(~data.key, t1);
+ var c1 = AesEnc(data.key, t1);
+ var t3 = Vector128.LoadUnsafe(in value[^(sizeof(ulong) * 8)]);
+ s1 = ShuffleAndAdd(s1, t3);
+ var c3 = AesEnc(data.key, t3);
+
+ for (var i = 0; i + sizeof(ulong) * 2 * 4 < value.Length; i += sizeof(ulong) * 2 * 4)
+ {
+ var b0 = Vector128.LoadUnsafe(
+ in Unsafe.Add(ref Unsafe.AsRef(in value[0]), i));
+ c0 = AesEnc(c0, b0);
+ s0 = ShuffleAndAdd(s0, b0);
+ var b1 = Vector128.LoadUnsafe(
+ in Unsafe.Add(ref Unsafe.AsRef(in value[0]), i + sizeof(ulong) * 2));
+ c1 = AesEnc(c1, b1);
+ s1 = ShuffleAndAdd(s1, b1);
+ var b2 = Vector128.LoadUnsafe(
+ in Unsafe.Add(ref Unsafe.AsRef(in value[0]), i + sizeof(ulong) * 4));
+ c2 = AesEnc(c2, b2);
+ s0 = ShuffleAndAdd(s0, b2);
+ var b3 = Vector128.LoadUnsafe(
+ in Unsafe.Add(ref Unsafe.AsRef(in value[0]), i + sizeof(ulong) * 6));
+ c3 = AesEnc(c3, b3);
+ s1 = ShuffleAndAdd(s1, b3);
+ }
+
+ data = Add(data, c0);
+ data = Add(data, c1);
+ data = Add(data, c2);
+ data = Add(data, c3);
+ data = Add(data, s0);
+ data = Add(data, s1);
+ return data;
+ }
+ }
+ else // 33 .. 64
+ {
+ return AddBytes_len_33_64(data, value);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ static AHasher2Data AddBytes_len_33_64(AHasher2Data data, ReadOnlySpan value)
+ {
+ var a = Vector128.LoadUnsafe(in value[0]);
+ var b = Vector128.LoadUnsafe(in value[sizeof(ulong) * 2]);
+ var c = Vector128.LoadUnsafe(in value[^(sizeof(ulong) * 4)]);
+ var d = Vector128.LoadUnsafe(in value[^(sizeof(ulong) * 2)]);
+ data = Add(data, a);
+ data = Add(data, b);
+ data = Add(data, c);
+ data = Add(data, d);
+ return data;
+ }
+ }
+ }
+ else if (value.Length > 16) // 17 .. 32
+ {
+ return AddBytes_len_17_32(data, value);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ static AHasher2Data AddBytes_len_17_32(AHasher2Data data, ReadOnlySpan value)
+ {
+ var a = Vector128.LoadUnsafe(in value[0]);
+ var b = Vector128.LoadUnsafe(in value[^(sizeof(ulong) * 2)]);
+ data = Add(data, a);
+ data = Add(data, b);
+ return data;
+ }
+ }
+ else // 9 .. 16
+ {
+ return AddBytes_len_9_16(data, value);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ static AHasher2Data AddBytes_len_9_16(AHasher2Data data, ReadOnlySpan value)
+ {
+ ref var addr = ref Unsafe.AsRef(in value[0]);
+ var a = Unsafe.As(ref addr);
+ var b = Unsafe.As(ref Unsafe.Add(ref addr, value.Length - sizeof(ulong)));
+ var v = Vector128.Create(a, b).AsByte();
+ return Add(data, v);
+ }
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static AHasher2Data AddString(AHasher2Data data, ReadOnlySpan value)
+ {
+ if (value.Length > 8)
+ {
+ return AddString_large(data, value);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ static AHasher2Data AddString_large(AHasher2Data data, ReadOnlySpan value)
+ {
+ data = AddBytes(data, value);
+ data.enc = AesEnc(data.sum, data.enc);
+ data.enc = AesDec(AesDec(data.enc, data.key), data.enc);
+ return data;
+ }
+ }
+ else
+ {
+ return AddString_small(data, value);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ static AHasher2Data AddString_small(AHasher2Data data, ReadOnlySpan value)
+ {
+ data = AddLength(data, value.Length);
+
+ var a = ReadSmall(value);
+ data.sum = ShuffleAndAdd(data.sum, a);
+ data.enc = AesEnc(data.sum, data.enc);
+ data.enc = AesDec(AesDec(data.enc, data.key), data.enc);
+
+ return data;
+ }
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static AHasher2Data AddString(AHasher2Data data, ReadOnlySpan value) =>
+ AddString(data, MemoryMarshal.Cast(value));
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static int ToHashCode(AHasher2Data data)
+ {
+ var combined = AesEnc(data.sum, data.enc);
+ var result = AesDec(AesDec(combined, data.key), combined).AsInt32();
+ return result[0];
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static long ToHashCodeLong(AHasher2Data data)
+ {
+ var combined = AesEnc(data.sum, data.enc);
+ var result = AesDec(AesDec(combined, data.key), combined).AsInt64();
+ return result[0];
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ private static AHasher2Data AddLength(AHasher2Data data, int len)
+ {
+ data.enc = (data.enc.AsInt32() + Vector128.CreateScalar(len)).AsByte();
+ return data;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ private static Vector128 ReadSmall(ReadOnlySpan value)
+ {
+ Debug.Assert(value.Length is < 8 and >= 0);
+ ref var addr = ref Unsafe.AsRef(in value[0]);
+ return value.Length switch
+ {
+ 0 => Vector128.Zero,
+ 1 => Vector128.CreateScalar(addr).AsByte(),
+ 2 => Vector128.CreateScalar(Unsafe.As(ref addr)).AsByte(),
+ 3 => Vector128.Create(Unsafe.As(ref addr), Unsafe.Add(ref addr, 2)).AsByte(),
+ 4 => Vector128.CreateScalar(Unsafe.As(ref addr)).AsByte(),
+ 5 => Vector128.Create(Unsafe.As(ref addr),
+ Unsafe.As(ref Unsafe.Add(ref addr, 5 - sizeof(uint)))).AsByte(),
+ 6 => Vector128.Create(Unsafe.As(ref addr),
+ Unsafe.As(ref Unsafe.Add(ref addr, 6 - sizeof(uint)))).AsByte(),
+ 7 => Vector128.Create(Unsafe.As(ref addr),
+ Unsafe.As(ref Unsafe.Add(ref addr, 7 - sizeof(uint)))).AsByte(),
+ _ => throw new ArgumentOutOfRangeException(nameof(value)),
+ };
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ private static Vector128 AesDec(Vector128 value, Vector128 xor)
+ {
+ if (X86.Aes.IsSupported)
+ {
+ return X86.Aes.Decrypt(value, xor);
+ }
+ else if (Arm.Aes.IsSupported)
+ {
+ var a = Arm.Aes.Decrypt(value, Vector128.Zero);
+ a = Arm.Aes.InverseMixColumns(a);
+ return xor ^ a;
+ }
+ else throw new PlatformNotSupportedException();
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ private static Vector128 AesEnc(Vector128 value, Vector128 xor)
+ {
+ if (X86.Aes.IsSupported)
+ {
+ return X86.Aes.Encrypt(value, xor);
+ }
+ else if (Arm.Aes.IsSupported)
+ {
+ var a = Arm.Aes.Encrypt(value, Vector128.Zero);
+ a = Arm.Aes.MixColumns(a);
+ return xor ^ a;
+ }
+ else throw new PlatformNotSupportedException();
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ private static Vector128 ShuffleAndAdd(Vector128 a, Vector128 b)
+ {
+ var shuffled = Vector128.Shuffle(a, ShuffleMask);
+ return AddByU64(shuffled, b);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ private static Vector128 AddByU64(Vector128 a, Vector128 b) =>
+ (a.AsUInt64() + b.AsUInt64()).AsByte();
+
+ private static readonly Vector128 ShuffleMask =
+ Vector128.Create(
+ (byte)0x02, 0x0a, 0x07, 0x00, 0x0c, 0x01, 0x03, 0x0e, 0x05, 0x0f, 0x0d, 0x08, 0x06, 0x09, 0x0b, 0x04);
+
+ #region Combine
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static int Combine(T1 value1)
+ {
+ var data = Create(AHasher._globalRandomState);
+ data = Add(data, value1);
+ return ToHashCode(data);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static int Combine(T1 value1, T2 value2)
+ {
+ var data = Create(AHasher._globalRandomState);
+ data = Add(data, value1);
+ data = Add(data, value2);
+ return ToHashCode(data);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static int Combine(T1 value1, T2 value2, T3 value3)
+ {
+ var data = Create(AHasher._globalRandomState);
+ data = Add(data, value1);
+ data = Add(data, value2);
+ data = Add(data, value3);
+ return ToHashCode(data);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4)
+ {
+ var data = Create(AHasher._globalRandomState);
+ data = Add(data, value1);
+ data = Add(data, value2);
+ data = Add(data, value3);
+ data = Add(data, value4);
+ return ToHashCode(data);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5)
+ {
+ var data = Create(AHasher._globalRandomState);
+ data = Add(data, value1);
+ data = Add(data, value2);
+ data = Add(data, value3);
+ data = Add(data, value4);
+ data = Add(data, value5);
+ return ToHashCode(data);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6)
+ {
+ var data = Create(AHasher._globalRandomState);
+ data = Add(data, value1);
+ data = Add(data, value2);
+ data = Add(data, value3);
+ data = Add(data, value4);
+ data = Add(data, value5);
+ data = Add(data, value6);
+ return ToHashCode(data);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5,
+ T6 value6, T7 value7)
+ {
+ var data = Create(AHasher._globalRandomState);
+ data = Add(data, value1);
+ data = Add(data, value2);
+ data = Add(data, value3);
+ data = Add(data, value4);
+ data = Add(data, value5);
+ data = Add(data, value6);
+ data = Add(data, value7);
+ return ToHashCode(data);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5,
+ T6 value6, T7 value7, T8 value8)
+ {
+ var data = Create(AHasher._globalRandomState);
+ data = Add(data, value1);
+ data = Add(data, value2);
+ data = Add(data, value3);
+ data = Add(data, value4);
+ data = Add(data, value5);
+ data = Add(data, value6);
+ data = Add(data, value7);
+ data = Add(data, value8);
+ return ToHashCode(data);
+ }
+
+ #endregion
+}
+
+#endif
diff --git a/BetterCollections/Cryptography/AHasher/SoftHasher.cs b/BetterCollections/Cryptography/AHasher/SoftHasher.cs
new file mode 100644
index 0000000..4fef73a
--- /dev/null
+++ b/BetterCollections/Cryptography/AHasher/SoftHasher.cs
@@ -0,0 +1,297 @@
+#if NET8_0_OR_GREATER
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
+
+namespace BetterCollections.Cryptography.AHasherImpl;
+
+public static class SoftHasher
+{
+ private static readonly Vector128 PI_1 = Vector128.Create(0x243f_6a88_85a3_08d3UL, 0x1319_8a2e_0370_7344UL);
+ private static readonly Vector128 PI_2 = Vector128.Create(0xa409_3822_299f_31d0UL, 0x082e_fa98_ec4e_6c89UL);
+
+ ///
+ /// This constant comes from Kunth's prng (Empirically it works better than those from splitmix32)
+ ///
+ private const ulong MULTIPLE = 6364136223846793005;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static AHasher2Data Create(AHasherRandomState randomState)
+ {
+ var key1 = randomState.key1.AsUInt64() ^ PI_1;
+ var key2 = randomState.key2.AsUInt64() ^ PI_2;
+ return new AHasher2Data(key1.GetElement(0), key1.GetElement(1), key2.AsByte());
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization), SkipLocalsInit]
+ public static AHasher2Data Add(AHasher2Data data, int value) => Update(data, (ulong)value);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization), SkipLocalsInit]
+ public static AHasher2Data Add(AHasher2Data data, uint value) => Update(data, value);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization), SkipLocalsInit]
+ public static AHasher2Data Add(AHasher2Data data, long value) => Update(data, (ulong)value);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization), SkipLocalsInit]
+ public static AHasher2Data Add(AHasher2Data data, ulong value) => Update(data, value);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization), SkipLocalsInit]
+ public static AHasher2Data Add(AHasher2Data data, T value)
+ {
+ return Update(data, (ulong)(value?.GetHashCode() ?? 0));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static AHasher2Data Add(AHasher2Data data, T value, IEqualityComparer? comparer)
+ {
+ if (comparer == null) return Add(data, value);
+ else return Update(data, (ulong)(value == null ? 0 : comparer.GetHashCode(value)));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static AHasher2Data AddBytes(AHasher2Data data, ReadOnlySpan value)
+ {
+ data.buffer = unchecked((data.buffer + (ulong)value.Length) * MULTIPLE);
+
+ if (value.Length > 8)
+ {
+ if (value.Length > 16)
+ {
+ return AddBytes_len_large(data, value);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ static AHasher2Data AddBytes_len_large(AHasher2Data data, ReadOnlySpan value)
+ {
+ ref var addr = ref Unsafe.AsRef(in value[0]);
+ var tail = Vector128.LoadUnsafe(in Unsafe.Add(ref addr, value.Length - sizeof(ulong) * 2));
+ data = UpdateLarge(data, tail);
+
+ for (var i = 0; i + sizeof(ulong) * 2 < value.Length; i += sizeof(ulong) * 2)
+ {
+ var item = Vector128.LoadUnsafe(in Unsafe.Add(ref addr, i));
+ data = UpdateLarge(data, item);
+ }
+
+ return data;
+ }
+ }
+ else
+ {
+ return AddBytes_len_16(data, value);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ static AHasher2Data AddBytes_len_16(AHasher2Data data, ReadOnlySpan value)
+ {
+ ref var addr = ref Unsafe.AsRef(in value[0]);
+ var a = Unsafe.As(ref addr);
+ var b = Unsafe.As(ref Unsafe.Add(ref addr, value.Length - sizeof(ulong)));
+ var v = Vector128.Create(a, b).AsByte();
+ return UpdateLarge(data, v);
+ }
+ }
+ }
+ else
+ {
+ return AddBytes_len_8(data, value);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ static AHasher2Data AddBytes_len_8(AHasher2Data data, ReadOnlySpan value) =>
+ UpdateLarge(data, ReadSmall(value));
+ }
+
+ return data;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static AHasher2Data AddString(AHasher2Data data, ReadOnlySpan value)
+ {
+ if (value.Length > 8)
+ {
+ return AddBytes(data, value);
+ }
+ else
+ {
+ return AddString_len_8(data, value);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ static AHasher2Data AddString_len_8(AHasher2Data data, ReadOnlySpan value)
+ {
+ var a = ReadSmall(value).AsUInt64() ^ data.buffer_extra1;
+ data.buffer = FoldedMultiply(a.GetElement(0), a.GetElement(1));
+ data.pad = unchecked(data.pad + (ulong)value.Length);
+ return data ;
+ }
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static AHasher2Data AddString(AHasher2Data data, ReadOnlySpan value) =>
+ AddString(data, MemoryMarshal.Cast(value));
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static int ToHashCode(AHasher2Data data)
+ => (int)ToHashCodeLong(data);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static long ToHashCodeLong(AHasher2Data data)
+ {
+ var rot = (int)(data.buffer & 63);
+ return (long)BitOperations.RotateLeft(FoldedMultiply(data.buffer, data.pad), rot);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ private static AHasher2Data Update(AHasher2Data data, ulong value)
+ {
+ data.buffer = FoldedMultiply(value ^ data.buffer);
+ return data;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ private static AHasher2Data UpdateLarge(AHasher2Data data, Vector128 value)
+ {
+ var a = value.AsUInt64() ^ data.extra.AsUInt64();
+ var combined = FoldedMultiply(a.GetElement(0), a.GetElement(1));
+ data.buffer = BitOperations.RotateLeft(unchecked(data.buffer + data.pad) ^ combined, 23);
+ return data;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static ulong FoldedMultiply(ulong s)
+ {
+ var result = unchecked((UInt128)s * MULTIPLE);
+ return (ulong)(result & 0xffff_ffff_ffff_ffff) ^ (ulong)(result >> 64);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static ulong FoldedMultiply(ulong s, ulong by)
+ {
+ var result = unchecked((UInt128)s * by);
+ return (ulong)(result & 0xffff_ffff_ffff_ffff) ^ (ulong)(result >> 64);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ private static Vector128 ReadSmall(ReadOnlySpan value)
+ {
+ Debug.Assert(value.Length is < 8 and >= 0);
+ ref var addr = ref Unsafe.AsRef(in value[0]);
+ return value.Length switch
+ {
+ 0 => Vector128.Zero,
+ 1 => Vector128.CreateScalar(addr).AsByte(),
+ 2 => Vector128.CreateScalar(Unsafe.As(ref addr)).AsByte(),
+ 3 => Vector128.Create(Unsafe.As(ref addr), Unsafe.Add(ref addr, 2)).AsByte(),
+ 4 => Vector128.CreateScalar(Unsafe.As(ref addr)).AsByte(),
+ 5 => Vector128.Create(Unsafe.As(ref addr),
+ Unsafe.As(ref Unsafe.Add(ref addr, 5 - sizeof(uint)))).AsByte(),
+ 6 => Vector128.Create(Unsafe.As(ref addr),
+ Unsafe.As(ref Unsafe.Add(ref addr, 6 - sizeof(uint)))).AsByte(),
+ 7 => Vector128.Create(Unsafe.As(ref addr),
+ Unsafe.As(ref Unsafe.Add(ref addr, 7 - sizeof(uint)))).AsByte(),
+ _ => throw new ArgumentOutOfRangeException(nameof(value)),
+ };
+ }
+
+ #region Combine
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static int Combine(T1 value1)
+ {
+ var data = Create(AHasher._globalRandomState);
+ data = Add(data, value1);
+ return ToHashCode(data);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static int Combine(T1 value1, T2 value2)
+ {
+ var data = Create(AHasher._globalRandomState);
+ data = Add(data, value1);
+ data = Add(data, value2);
+ return ToHashCode(data);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static int Combine(T1 value1, T2 value2, T3 value3)
+ {
+ var data = Create(AHasher._globalRandomState);
+ data = Add(data, value1);
+ data = Add(data, value2);
+ data = Add(data, value3);
+ return ToHashCode(data);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4)
+ {
+ var data = Create(AHasher._globalRandomState);
+ data = Add(data, value1);
+ data = Add(data, value2);
+ data = Add(data, value3);
+ data = Add(data, value4);
+ return ToHashCode(data);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5)
+ {
+ var data = Create(AHasher._globalRandomState);
+ data = Add(data, value1);
+ data = Add(data, value2);
+ data = Add(data, value3);
+ data = Add(data, value4);
+ data = Add(data, value5);
+ return ToHashCode(data);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6)
+ {
+ var data = Create(AHasher._globalRandomState);
+ data = Add(data, value1);
+ data = Add(data, value2);
+ data = Add(data, value3);
+ data = Add(data, value4);
+ data = Add(data, value5);
+ data = Add(data, value6);
+ return ToHashCode(data);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5,
+ T6 value6, T7 value7)
+ {
+ var data = Create(AHasher._globalRandomState);
+ data = Add(data, value1);
+ data = Add(data, value2);
+ data = Add(data, value3);
+ data = Add(data, value4);
+ data = Add(data, value5);
+ data = Add(data, value6);
+ data = Add(data, value7);
+ return ToHashCode(data);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit]
+ public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5,
+ T6 value6, T7 value7, T8 value8)
+ {
+ var data = Create(AHasher._globalRandomState);
+ data = Add(data, value1);
+ data = Add(data, value2);
+ data = Add(data, value3);
+ data = Add(data, value4);
+ data = Add(data, value5);
+ data = Add(data, value6);
+ data = Add(data, value7);
+ data = Add(data, value8);
+ return ToHashCode(data);
+ }
+
+ #endregion
+}
+
+#endif
diff --git a/BetterCollections/Cryptography/AHasher.cs b/BetterCollections/Cryptography/AHasherLegacy.cs
similarity index 95%
rename from BetterCollections/Cryptography/AHasher.cs
rename to BetterCollections/Cryptography/AHasherLegacy.cs
index db1f158..a495ecd 100644
--- a/BetterCollections/Cryptography/AHasher.cs
+++ b/BetterCollections/Cryptography/AHasherLegacy.cs
@@ -17,7 +17,7 @@ namespace BetterCollections.Cryptography;
/// A hasher that ensures even distribution of each bit
/// If possible use Aes SIMD acceleration (.net7+)
///
-public readonly struct AHasher : IHasher
+public readonly struct AHasherLegacy : IHasherLegacy
{
#if NET7_0_OR_GREATER
private readonly Union union;
@@ -43,7 +43,7 @@ public Union(SoftHasher softHasher)
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public AHasher()
+ public AHasherLegacy()
{
var rand = Random.Shared;
Span> bytes = stackalloc Vector128[2];
@@ -56,15 +56,16 @@ public AHasher()
else
{
union = new(new SoftHasher(MemoryMarshal.Cast, ulong>(bytes)));
+ soft = true;
}
}
[ThreadStatic]
- private static AHasher _thread_current;
+ private static AHasherLegacy _thread_current;
[ThreadStatic]
private static bool _thread_current_has;
- public static AHasher ThreadCurrent
+ public static AHasherLegacy ThreadCurrent
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
@@ -78,7 +79,7 @@ public static AHasher ThreadCurrent
}
}
- public AHasher(ReadOnlySpan keys)
+ public AHasherLegacy(ReadOnlySpan keys)
{
if (keys.Length < 4) throw new ArgumentOutOfRangeException(nameof(keys), "length of keys must >= 4");
if (X86.Aes.IsSupported || Arm.Aes.IsSupported)
@@ -148,7 +149,7 @@ private static Vector128 AesEnc(Vector128 value, Vector128 xor
private readonly SoftHasher softHasher;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public AHasher()
+ public AHasherLegacy()
{
#if NETSTANDARD
var rand = new Random();
@@ -160,7 +161,7 @@ public AHasher()
softHasher = new(keys);
}
- public AHasher(ReadOnlySpan keys)
+ public AHasherLegacy(ReadOnlySpan keys)
{
if (keys.Length < 4) throw new ArgumentOutOfRangeException(nameof(keys), "length of keys must >= 4");
softHasher = new(keys);
diff --git a/BetterCollections/Cryptography/IHasher.cs b/BetterCollections/Cryptography/IHasher.cs
index b2fe120..063cab1 100644
--- a/BetterCollections/Cryptography/IHasher.cs
+++ b/BetterCollections/Cryptography/IHasher.cs
@@ -1,6 +1,15 @@
-namespace BetterCollections.Cryptography;
+using System;
+using System.Collections.Generic;
+
+namespace BetterCollections.Cryptography;
public interface IHasher
{
- public ulong Hash(ulong value);
+ public void Add(T value);
+ public void Add(T value, IEqualityComparer? comparer);
+ public void AddBytes(ReadOnlySpan value);
+ public void AddString(ReadOnlySpan value);
+ public void AddString(ReadOnlySpan value);
+ public int ToHashCode();
+ public long ToHashCodeLong();
}
diff --git a/BetterCollections/Cryptography/IHasherLegacy.cs b/BetterCollections/Cryptography/IHasherLegacy.cs
new file mode 100644
index 0000000..0b7c3dd
--- /dev/null
+++ b/BetterCollections/Cryptography/IHasherLegacy.cs
@@ -0,0 +1,6 @@
+namespace BetterCollections.Cryptography;
+
+public interface IHasherLegacy
+{
+ public ulong Hash(ulong value);
+}
diff --git a/BetterCollections/Misc/SkipLocalsInitAttribute.cs b/BetterCollections/Misc/SkipLocalsInitAttribute.cs
new file mode 100644
index 0000000..6667840
--- /dev/null
+++ b/BetterCollections/Misc/SkipLocalsInitAttribute.cs
@@ -0,0 +1,13 @@
+#if NETSTANDARD
+namespace System.Runtime.CompilerServices;
+
+[AttributeUsage(AttributeTargets.Module
+ | AttributeTargets.Class
+ | AttributeTargets.Struct
+ | AttributeTargets.Interface
+ | AttributeTargets.Constructor
+ | AttributeTargets.Method
+ | AttributeTargets.Property
+ | AttributeTargets.Event, Inherited = false)]
+internal sealed class SkipLocalsInitAttribute : Attribute;
+#endif
diff --git a/BetterCollections/Misc/UnscopedRefAttribute.cs b/BetterCollections/Misc/UnscopedRefAttribute.cs
new file mode 100644
index 0000000..01a3db6
--- /dev/null
+++ b/BetterCollections/Misc/UnscopedRefAttribute.cs
@@ -0,0 +1,7 @@
+#if NETSTANDARD || NET6_0
+namespace System.Diagnostics.CodeAnalysis;
+
+[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Parameter, Inherited = false)]
+internal sealed class UnscopedRefAttribute : Attribute;
+
+#endif
diff --git a/BetterCollections/Misc/Utils.cs b/BetterCollections/Misc/Utils.cs
index 288f642..86958df 100644
--- a/BetterCollections/Misc/Utils.cs
+++ b/BetterCollections/Misc/Utils.cs
@@ -185,6 +185,16 @@ public static string ToBinaryString(this ulong value)
return Regex.Replace(a, ".{8}(?!$)", "$0_");
#endif
}
+
+ public static string ToBinaryString(this uint value)
+ {
+ var a = Convert.ToString((int)value, 2).PadLeft(32, '0');
+#if NET8_0_OR_GREATER
+ return SplitBytes().Replace(a, "$0_");
+#else
+ return Regex.Replace(a, ".{8}(?!$)", "$0_");
+#endif
+ }
///
/// Evaluate whether a given integral value is a power of 2.
diff --git a/Tests/TestAHasher.cs b/Tests/TestAHasher.cs
index 80ec438..a3dfc83 100644
--- a/Tests/TestAHasher.cs
+++ b/Tests/TestAHasher.cs
@@ -1,4 +1,6 @@
-using BetterCollections.Cryptography;
+#if NET8_0_OR_GREATER
+
+using BetterCollections.Cryptography;
using BetterCollections.Misc;
namespace Tests;
@@ -8,20 +10,19 @@ public class TestAHasher
[Test, Parallelizable]
public void Test1()
{
- var counts = new int[64];
+ var counts = new int[32];
var count = 0;
for (int k = 0; k < 100; k++)
{
- var hasher = new AHasher();
ParallelEnumerable.Range(-500, 1000)
.ForAll(i =>
{
- var a = hasher.Hash((ulong)i);
+ var a = (uint)AHasher.Combine((ulong)i);
var s = a.ToBinaryString();
var ss = s.Replace("_", "");
Interlocked.Increment(ref count);
- for (var j = 0; j < 64; j++)
+ for (var j = 0; j < 32; j++)
{
if (ss[j] == '1') Interlocked.Increment(ref counts[j]);
}
@@ -39,11 +40,11 @@ public void Test1()
Assert.That(Math.Abs(Math.Round(ave * 10) - 5), Is.LessThan(0.1d));
}
-
+
[Test, Parallelizable]
public void TestSystemHash()
{
- var counts = new int[64];
+ var counts = new int[32];
var count = 0;
for (int k = 0; k < 100; k++)
@@ -52,10 +53,10 @@ public void TestSystemHash()
.ForAll(i =>
{
var a = HashCode.Combine(i);
- var s = ((ulong)a).ToBinaryString();
+ var s = ((uint)a).ToBinaryString();
var ss = s.Replace("_", "");
Interlocked.Increment(ref count);
- for (var j = 0; j < 64; j++)
+ for (var j = 0; j < 32; j++)
{
if (ss[j] == '1') Interlocked.Increment(ref counts[j]);
}
@@ -74,3 +75,5 @@ public void TestSystemHash()
Assert.That(Math.Abs(Math.Round(ave * 10) - 5), Is.LessThan(0.1d));
}
}
+
+#endif
diff --git a/Tests/TestAHasherLegacy.cs b/Tests/TestAHasherLegacy.cs
new file mode 100644
index 0000000..b5f7d27
--- /dev/null
+++ b/Tests/TestAHasherLegacy.cs
@@ -0,0 +1,76 @@
+using BetterCollections.Cryptography;
+using BetterCollections.Misc;
+
+namespace Tests;
+
+public class TestAHasherLegacy
+{
+ [Test, Parallelizable]
+ public void Test1()
+ {
+ var counts = new int[64];
+ var count = 0;
+
+ for (int k = 0; k < 100; k++)
+ {
+ var hasher = new AHasherLegacy();
+ ParallelEnumerable.Range(-500, 1000)
+ .ForAll(i =>
+ {
+ var a = hasher.Hash((ulong)i);
+ var s = a.ToBinaryString();
+ var ss = s.Replace("_", "");
+ Interlocked.Increment(ref count);
+ for (var j = 0; j < 64; j++)
+ {
+ if (ss[j] == '1') Interlocked.Increment(ref counts[j]);
+ }
+ });
+ }
+
+ Console.WriteLine();
+ Console.WriteLine(count);
+ Console.WriteLine(string.Join(", ", counts.Select(a => a / (double)count)));
+ Console.WriteLine();
+ var ave = counts.Average(a => a / (double)count);
+ Console.WriteLine($"Average: {ave * 100} %");
+ var round = Math.Round(ave * 100);
+ Console.WriteLine($"Rounded: {round} %");
+
+ Assert.That(Math.Abs(Math.Round(ave * 10) - 5), Is.LessThan(0.1d));
+ }
+
+ [Test, Parallelizable]
+ public void TestSystemHash()
+ {
+ var counts = new int[64];
+ var count = 0;
+
+ for (int k = 0; k < 100; k++)
+ {
+ ParallelEnumerable.Range(-500, 1000)
+ .ForAll(i =>
+ {
+ var a = HashCode.Combine(i);
+ var s = ((ulong)a).ToBinaryString();
+ var ss = s.Replace("_", "");
+ Interlocked.Increment(ref count);
+ for (var j = 0; j < 64; j++)
+ {
+ if (ss[j] == '1') Interlocked.Increment(ref counts[j]);
+ }
+ });
+ }
+
+ Console.WriteLine();
+ Console.WriteLine(count);
+ Console.WriteLine(string.Join(", ", counts.Select(a => a / (double)count)));
+ Console.WriteLine();
+ var ave = counts.Average(a => a / (double)count);
+ Console.WriteLine($"Average: {ave * 100} %");
+ var round = Math.Round(ave * 100);
+ Console.WriteLine($"Rounded: {round} %");
+
+ Assert.That(Math.Abs(Math.Round(ave * 10) - 5), Is.LessThan(0.1d));
+ }
+}