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)); + } +}