From 542e658a17c58cb99e399205f4138fd5a1ce3747 Mon Sep 17 00:00:00 2001 From: U2A5F Date: Wed, 27 Dec 2023 19:36:18 +0800 Subject: [PATCH 1/7] save --- Benchmark/BenchmarkAHash.cs | 29 ++ BetterCollections/Cryptography/AHasher.cs | 1 + .../Cryptography/AHasher/AHasher2.cs | 242 ++++++++++++++ .../Cryptography/AHasher/AHasher2Data.cs | 57 ++++ .../AHasher/AHasherRandomState.cs | 88 +++++ .../Cryptography/AHasher/AesHasher.cs | 305 ++++++++++++++++++ .../Cryptography/AHasher/SoftHasher.cs | 63 ++++ BetterCollections/Cryptography/IHasher2.cs | 15 + .../Misc/SkipLocalsInitAttribute.cs | 13 + .../Misc/UnscopedRefAttribute.cs | 7 + 10 files changed, 820 insertions(+) create mode 100644 BetterCollections/Cryptography/AHasher/AHasher2.cs create mode 100644 BetterCollections/Cryptography/AHasher/AHasher2Data.cs create mode 100644 BetterCollections/Cryptography/AHasher/AHasherRandomState.cs create mode 100644 BetterCollections/Cryptography/AHasher/AesHasher.cs create mode 100644 BetterCollections/Cryptography/AHasher/SoftHasher.cs create mode 100644 BetterCollections/Cryptography/IHasher2.cs create mode 100644 BetterCollections/Misc/SkipLocalsInitAttribute.cs create mode 100644 BetterCollections/Misc/UnscopedRefAttribute.cs diff --git a/Benchmark/BenchmarkAHash.cs b/Benchmark/BenchmarkAHash.cs index ba9caa9..9786e9d 100644 --- a/Benchmark/BenchmarkAHash.cs +++ b/Benchmark/BenchmarkAHash.cs @@ -1,6 +1,7 @@ using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Diagnostics.Windows.Configs; using BetterCollections.Cryptography; +using BetterCollections.Cryptography.AHasherImpl; namespace Benchmark; @@ -22,6 +23,34 @@ public ulong AHash() return r; } + [Benchmark] + public int AHash2() + { + var r = 0; + for (int i = 0; i < 1000; i++) + { + r += AHasher2.Combine(i); + } + return r; + } + +#if NET7_0_OR_GREATER + + [Benchmark] + public int AHash3() + { + var r = 0; + for (int i = 0; i < 1000; i++) + { + var hasher = new AesHasher(AHasher2.GlobalRandomState); + hasher.Add(i); + r += hasher.ToHashCode(); + } + return r; + } + +#endif + [Benchmark(Baseline = true)] public int HashCodeCombine() { diff --git a/BetterCollections/Cryptography/AHasher.cs b/BetterCollections/Cryptography/AHasher.cs index db1f158..80ae5c4 100644 --- a/BetterCollections/Cryptography/AHasher.cs +++ b/BetterCollections/Cryptography/AHasher.cs @@ -56,6 +56,7 @@ public AHasher() else { union = new(new SoftHasher(MemoryMarshal.Cast, ulong>(bytes))); + soft = true; } } diff --git a/BetterCollections/Cryptography/AHasher/AHasher2.cs b/BetterCollections/Cryptography/AHasher/AHasher2.cs new file mode 100644 index 0000000..bdacfff --- /dev/null +++ b/BetterCollections/Cryptography/AHasher/AHasher2.cs @@ -0,0 +1,242 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using BetterCollections.Cryptography.AHasherImpl; + +#if NET7_0_OR_GREATER +using System.Runtime.Intrinsics; +using X86 = System.Runtime.Intrinsics.X86; +using Arm = System.Runtime.Intrinsics.Arm; +#endif + +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 partial struct AHasher2 : IHasher2 +{ + #region GlobalRandomState + + public static AHasherRandomState GlobalRandomState + { + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + get; + } = 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() + { +#if NETSTANDARD + var rand = new Random(); +#else + var rand = Random.Shared; +#endif + Unsafe.SkipInit(out AHasherRandomState state); + rand.NextBytes(MemoryMarshal.Cast(state.AsSpan())); + return state; + } + + #endregion + + #region Create + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public static AHasher2 CreateGlobal() => new(GlobalRandomState); + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public static AHasher2 CreateThreadCurrent() => new(ThreadCurrentRandomState); + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public static AHasher2 CreateRandom() => new(GenerateRandomState()); + + #endregion + + #region Impl + +#if NET7_0_OR_GREATER + private Union union; + + [StructLayout(LayoutKind.Explicit)] + private struct Union + { + [FieldOffset(0)] + public AesHasher aesHasher; + [FieldOffset(0)] + public SoftHasher softHasher; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public AHasher2(AHasherRandomState randomState) + { + Unsafe.SkipInit(out this); + if (AesHasher.IsSupported) + { + union.aesHasher = new AesHasher(randomState); + } + else + { + union.softHasher = new SoftHasher(randomState); + } + } + + #region IHasher + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public void Add(int value) + { + if (AesHasher.IsSupported) union.aesHasher.Add(value); + else union.softHasher.Add(value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public void Add(long value) + { + if (AesHasher.IsSupported) union.aesHasher.Add(value); + else union.softHasher.Add(value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public void Add(T value) + { + if (AesHasher.IsSupported) union.aesHasher.Add(value); + else union.softHasher.Add(value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public void Add(T value, IEqualityComparer? comparer) + { + if (AesHasher.IsSupported) union.aesHasher.Add(value, comparer); + else union.softHasher.Add(value, comparer); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public void AddBytes(ReadOnlySpan value) + { + if (AesHasher.IsSupported) union.aesHasher.AddBytes(value); + else union.softHasher.AddBytes(value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public void AddString(ReadOnlySpan value) + { + if (AesHasher.IsSupported) union.aesHasher.AddString(value); + else union.softHasher.AddString(value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public void AddString(ReadOnlySpan value) + { + if (AesHasher.IsSupported) union.aesHasher.AddString(value); + else union.softHasher.AddString(value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public int ToHashCode() => AesHasher.IsSupported ? union.aesHasher.ToHashCode() : union.softHasher.ToHashCode(); + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public long ToHashCodeLong() => + AesHasher.IsSupported ? union.aesHasher.ToHashCodeLong() : union.softHasher.ToHashCodeLong(); + + #endregion + +#else + private SoftHasher softHasher; + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public AHasher2(SoftHasher softHasher) + { + Unsafe.SkipInit(out this); + this.softHasher = softHasher; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public AHasher2(AHasherRandomState randomState) + { + Unsafe.SkipInit(out this); + softHasher = new SoftHasher(randomState); + } + + #region IHasher + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public void Add(int value) => softHasher.Add(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public void Add(long value) => softHasher.Add(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public void Add(T value) => softHasher.Add(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public void Add(T value, IEqualityComparer? comparer) => softHasher.Add(value, comparer); + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public void AddBytes(ReadOnlySpan value) => softHasher.AddBytes(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public void AddString(ReadOnlySpan value) => softHasher.AddString(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public void AddString(ReadOnlySpan value) => softHasher.AddString(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public int ToHashCode() => softHasher.ToHashCode(); + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public long ToHashCodeLong() => softHasher.ToHashCodeLong(); + + #endregion + +#endif + + #endregion + + #region Combine + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public static int Combine(T1 value1) + { + var hasher = CreateGlobal(); + hasher.Add(value1); + return hasher.ToHashCode(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public static int Combine(T1 value1, T2 value2) + { + var hasher = CreateGlobal(); + hasher.Add(value1); + hasher.Add(value2); + return hasher.ToHashCode(); + } + + #endregion +} diff --git a/BetterCollections/Cryptography/AHasher/AHasher2Data.cs b/BetterCollections/Cryptography/AHasher/AHasher2Data.cs new file mode 100644 index 0000000..a9cc5f6 --- /dev/null +++ b/BetterCollections/Cryptography/AHasher/AHasher2Data.cs @@ -0,0 +1,57 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +#if NET7_0_OR_GREATER +using System.Runtime.Intrinsics; +#endif + +namespace BetterCollections.Cryptography.AHasherImpl; + +public struct AHasher2Data +{ +#if NET7_0_OR_GREATER + 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; + } + + [UnscopedRef] + public ref ulong buffer + { + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + get => ref Unsafe.As(ref this); + } + + [UnscopedRef] + public ref ulong pad + { + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + get => ref Unsafe.Add(ref Unsafe.As(ref this), 1); + } +#else + public ulong buffer; + public ulong pad; +#endif + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public AHasher2Data(ulong buffer, ulong pad) + { + Unsafe.SkipInit(out this); + this.buffer = buffer; + this.pad = pad; + } +} diff --git a/BetterCollections/Cryptography/AHasher/AHasherRandomState.cs b/BetterCollections/Cryptography/AHasher/AHasherRandomState.cs new file mode 100644 index 0000000..845ec2a --- /dev/null +++ b/BetterCollections/Cryptography/AHasher/AHasherRandomState.cs @@ -0,0 +1,88 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +#if NET6_0_OR_GREATER +using System.Runtime.Intrinsics; +#endif + +namespace BetterCollections.Cryptography; + +#if NET8_0_OR_GREATER +[InlineArray(4)] +[StructLayout(LayoutKind.Sequential, Size = sizeof(ulong) * 4)] +public struct AHasherKeys +{ + public ulong key; + + [UnscopedRef] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref key, 4); +} +#endif + +[StructLayout(LayoutKind.Explicit)] +public struct AHasherRandomState +{ + [FieldOffset(0)] + public ulong a; + [FieldOffset(sizeof(ulong))] + public ulong b; + [FieldOffset(sizeof(ulong) * 2)] + public ulong c; + [FieldOffset(sizeof(ulong) * 3)] + public ulong d; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public AHasherRandomState(ulong a, ulong b, ulong c, ulong d) + { + Unsafe.SkipInit(out this); + this.a = a; + this.b = b; + this.c = c; + this.d = d; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public AHasherRandomState(ReadOnlySpan keys) + { + if (keys.Length < 4) throw new ArgumentOutOfRangeException(nameof(keys), "length of keys must >= 4"); + Unsafe.SkipInit(out this); + a = keys[0]; + b = keys[1]; + c = keys[2]; + d = keys[3]; + } + +#if NET8_0_OR_GREATER + [FieldOffset(0)] + public AHasherKeys keys; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public AHasherRandomState(AHasherKeys keys) + { + Unsafe.SkipInit(out this); + this.keys = keys; + } +#endif + +#if NET6_0_OR_GREATER + [FieldOffset(0)] + public Vector128 key1; + [FieldOffset(sizeof(ulong) * 2)] + public Vector128 key2; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public AHasherRandomState(Vector128 key1, Vector128 key2) + { + Unsafe.SkipInit(out this); + this.key1 = key1; + this.key2 = key2; + } +#endif + + [UnscopedRef] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref a, 4); +} diff --git a/BetterCollections/Cryptography/AHasher/AesHasher.cs b/BetterCollections/Cryptography/AHasher/AesHasher.cs new file mode 100644 index 0000000..732b322 --- /dev/null +++ b/BetterCollections/Cryptography/AHasher/AesHasher.cs @@ -0,0 +1,305 @@ +#if NET7_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; + +[method: MethodImpl(MethodImplOptions.AggressiveInlining)] +public struct AesHasher(AHasherRandomState randomState) : IHasher2 +{ + 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); + + private Vector128 enc = randomState.key1; + private Vector128 sum = randomState.key2; + private readonly Vector128 key = randomState.key1 ^ randomState.key2; + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public void Add(int value) => Add((long)value); + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public void Add(uint value) => Add((ulong)value); + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public void Add(long value) => Add(Vector128.CreateScalar(value)); + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public void Add(ulong value) => Add(Vector128.CreateScalar(value)); + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public void Add(Int128 value) => Add(Unsafe.As>(ref value)); + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public void Add(UInt128 value) => Add(Unsafe.As>(ref value)); + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public void Add(T value) + { + var type = typeof(T); + if (type == typeof(int)) Add(Unsafe.As(ref value)); + else if (type == typeof(uint)) Add(Unsafe.As(ref value)); + else if (type == typeof(long)) Add(Unsafe.As(ref value)); + else if (type == typeof(ulong)) Add(Unsafe.As(ref value)); + else if (type == typeof(Int128)) Add(Unsafe.As(ref value)); + else if (type == typeof(UInt128)) Add(Unsafe.As(ref value)); + else if (type == typeof(Vector128)) Add(Unsafe.As>(ref value)); + else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Vector128<>)) + Add(Unsafe.As>(ref value)); + else Add(value == null ? 0 : value.GetHashCode()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public void Add(T value, IEqualityComparer? comparer) + { + if (comparer == null) Add(value); + else Add(value == null ? 0 : comparer.GetHashCode(value)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public void Add(Vector128 value) +#if NET7_0 + where T : struct +#endif + => Add(value.AsByte()); + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public void Add(Vector128 value) + { + enc = AesDec(enc, value); + sum = ShuffleAndAdd(sum, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public void AddBytes(ReadOnlySpan value) + { + AddLength(value.Length); + + if (value.Length <= 8) + { + Add(ReadSmall(value)); + } + else if (value.Length > 32) + { + if (value.Length > 64) + { + Span> tail = stackalloc Vector128[4] +#if NET8_0_OR_GREATER + { + Vector128.LoadUnsafe(in value[^(sizeof(ulong) * 2)]), + Vector128.LoadUnsafe(in value[^(sizeof(ulong) * 4)]), + Vector128.LoadUnsafe(in value[^(sizeof(ulong) * 6)]), + Vector128.LoadUnsafe(in value[^(sizeof(ulong) * 8)]), + }; +#else + { + Vector128.LoadUnsafe(ref Unsafe.AsRef(in value[^(sizeof(ulong) * 2)])), + Vector128.LoadUnsafe(ref Unsafe.AsRef(in value[^(sizeof(ulong) * 4)])), + Vector128.LoadUnsafe(ref Unsafe.AsRef(in value[^(sizeof(ulong) * 6)])), + Vector128.LoadUnsafe(ref Unsafe.AsRef(in value[^(sizeof(ulong) * 8)])), + }; +#endif + Span> current = stackalloc Vector128[4] { key, key, key, key }; + current[0] = AesEnc(current[0], tail[0]); + current[1] = AesEnc(current[1], tail[1]); + current[2] = AesEnc(current[2], tail[2]); + current[3] = AesEnc(current[3], tail[3]); + Span> sum = stackalloc Vector128[2] { key, ~key }; + sum[0] = AddByU64(sum[0], tail[0]); + sum[1] = AddByU64(sum[1], tail[1]); + sum[0] = ShuffleAndAdd(sum[0], tail[2]); + sum[1] = ShuffleAndAdd(sum[1], tail[3]); + + // tail will not use again + for (; value.Length > 64; value = value[(sizeof(ulong) * 2 * 4)..]) + { + var blocks = tail; +#if NET8_0_OR_GREATER + blocks[0] = Vector128.LoadUnsafe(in value[0]); + blocks[1] = Vector128.LoadUnsafe(in value[sizeof(ulong) * 2]); + blocks[2] = Vector128.LoadUnsafe(in value[sizeof(ulong) * 4]); + blocks[3] = Vector128.LoadUnsafe(in value[sizeof(ulong) * 6]); +#else + blocks[0] = Vector128.LoadUnsafe(ref Unsafe.AsRef(in value[0])); + blocks[1] = Vector128.LoadUnsafe(ref Unsafe.AsRef(in value[sizeof(ulong) * 2])); + blocks[2] = Vector128.LoadUnsafe(ref Unsafe.AsRef(in value[sizeof(ulong) * 4])); + blocks[3] = Vector128.LoadUnsafe(ref Unsafe.AsRef(in value[sizeof(ulong) * 6])); +#endif + + current[0] = AesEnc(current[0], blocks[0]); + current[1] = AesEnc(current[1], blocks[1]); + current[2] = AesEnc(current[2], blocks[2]); + current[3] = AesEnc(current[3], blocks[3]); + sum[0] = ShuffleAndAdd(sum[0], blocks[0]); + sum[1] = ShuffleAndAdd(sum[1], blocks[1]); + sum[0] = ShuffleAndAdd(sum[0], blocks[2]); + sum[1] = ShuffleAndAdd(sum[1], blocks[3]); + } + + Add(current[0]); + Add(current[1]); + Add(current[2]); + Add(current[3]); + Add(sum[0]); + Add(sum[1]); + } + else // 33 .. 64 + { +#if NET8_0_OR_GREATER + 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)]); +#else + var a = Vector128.LoadUnsafe(ref Unsafe.AsRef(in value[0])); + var b = Vector128.LoadUnsafe(ref Unsafe.AsRef(in value[sizeof(ulong) * 2])); + var c = Vector128.LoadUnsafe(ref Unsafe.AsRef(in value[^(sizeof(ulong) * 4)])); + var d = Vector128.LoadUnsafe(ref Unsafe.AsRef(in value[^(sizeof(ulong) * 2)])); +#endif + Add(a); + Add(b); + Add(c); + Add(d); + } + } + else if (value.Length > 16) // 17 .. 32 + { +#if NET8_0_OR_GREATER + var a = Vector128.LoadUnsafe(in value[0]); + var b = Vector128.LoadUnsafe(in value[^(sizeof(ulong) * 2)]); +#else + var a = Vector128.LoadUnsafe(ref Unsafe.AsRef(in value[0])); + var b = Vector128.LoadUnsafe(ref Unsafe.AsRef(in value[^(sizeof(ulong) * 2)])); +#endif + Add(a); + Add(b); + } + else // 9 .. 16 + { + var a = Vector128.Create(MemoryMarshal.Cast(value)[0], + MemoryMarshal.Cast(value.Slice(value.Length - sizeof(ulong), sizeof(ulong)))[0]); + Add(a); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public void AddString(ReadOnlySpan value) + { + if (value.Length > 8) + { + AddBytes(value); + enc = AesEnc(sum, enc); + enc = AesDec(AesDec(enc, key), enc); + } + else + { + AddLength(value.Length); + + var a = ReadSmall(value); + sum = ShuffleAndAdd(sum, a); + enc = AesEnc(sum, enc); + enc = AesDec(AesDec(enc, key), enc); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public void AddString(ReadOnlySpan value) => AddString(MemoryMarshal.Cast(value)); + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public int ToHashCode() + { + var combined = AesEnc(sum, enc); + var result = AesDec(AesDec(combined, key), combined).AsInt32(); + return result[0]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public long ToHashCodeLong() + { + var combined = AesEnc(sum, enc); + var result = AesDec(AesDec(combined, key), combined).AsInt64(); + return result[0]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + private Vector128 ReadSmall(ReadOnlySpan value) + { + Debug.Assert(value.Length is < 8 and >= 0); + return value.Length switch + { + 0 => Vector128.Zero, + 1 => Vector128.Create((ulong)value[0], value[0]).AsByte(), + 2 or 3 => Vector128.Create((ulong)MemoryMarshal.Cast(value)[0], value[^1]).AsByte(), + _ => Vector128.Create((ulong)MemoryMarshal.Cast(value)[0], + MemoryMarshal.Cast(value.Slice(value.Length - sizeof(uint), sizeof(uint)))[0]) + .AsByte() + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + private void AddLength(int len) + { + enc = (enc.AsInt32() + Vector128.CreateScalar(len)).AsByte(); + } + + [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); +} + +#endif diff --git a/BetterCollections/Cryptography/AHasher/SoftHasher.cs b/BetterCollections/Cryptography/AHasher/SoftHasher.cs new file mode 100644 index 0000000..f8e10a5 --- /dev/null +++ b/BetterCollections/Cryptography/AHasher/SoftHasher.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace BetterCollections.Cryptography.AHasherImpl; + +public struct SoftHasher(AHasherRandomState randomState) : IHasher2 +{ + public ulong buffer = randomState.a; + public ulong pad = randomState.b; + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public void Add(int value) + { + throw new NotImplementedException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public void Add(long value) + { + throw new NotImplementedException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public void Add(T value) + { + throw new NotImplementedException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public void Add(T value, IEqualityComparer? comparer) + { + throw new NotImplementedException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public void AddBytes(ReadOnlySpan value) + { + throw new NotImplementedException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public void AddString(ReadOnlySpan value) + { + throw new NotImplementedException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public void AddString(ReadOnlySpan value) => AddString(MemoryMarshal.Cast(value)); + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public int ToHashCode() + { + throw new NotImplementedException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public long ToHashCodeLong() + { + throw new NotImplementedException(); + } +} diff --git a/BetterCollections/Cryptography/IHasher2.cs b/BetterCollections/Cryptography/IHasher2.cs new file mode 100644 index 0000000..b532092 --- /dev/null +++ b/BetterCollections/Cryptography/IHasher2.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; + +namespace BetterCollections.Cryptography; + +public interface IHasher2 +{ + 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/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 From 3fe47094697ce2db450d3f08a0c5c8cacca9ab51 Mon Sep 17 00:00:00 2001 From: U2A5F Date: Wed, 27 Dec 2023 21:36:46 +0800 Subject: [PATCH 2/7] impled --- Benchmark/BenchmarkAHash.cs | 10 +- .../Cryptography/AHasher/AHasher2.cs | 122 ++++++------ .../AHasher/AHasherRandomState.cs | 115 +++++------ .../Cryptography/AHasher/AesHasher.cs | 178 +++++++++++------- .../Cryptography/AHasher/SoftHasher.cs | 56 +----- 5 files changed, 241 insertions(+), 240 deletions(-) diff --git a/Benchmark/BenchmarkAHash.cs b/Benchmark/BenchmarkAHash.cs index 9786e9d..64e2d13 100644 --- a/Benchmark/BenchmarkAHash.cs +++ b/Benchmark/BenchmarkAHash.cs @@ -35,20 +35,20 @@ public int AHash2() } #if NET7_0_OR_GREATER - + [Benchmark] public int AHash3() { var r = 0; for (int i = 0; i < 1000; i++) { - var hasher = new AesHasher(AHasher2.GlobalRandomState); - hasher.Add(i); - r += hasher.ToHashCode(); + var hasher = AesHasher.Create(AHasher2.GlobalRandomState); + AesHasher.Add(ref hasher, i); + r += AesHasher.ToHashCode(ref hasher); } return r; } - + #endif [Benchmark(Baseline = true)] diff --git a/BetterCollections/Cryptography/AHasher/AHasher2.cs b/BetterCollections/Cryptography/AHasher/AHasher2.cs index bdacfff..dcaeaae 100644 --- a/BetterCollections/Cryptography/AHasher/AHasher2.cs +++ b/BetterCollections/Cryptography/AHasher/AHasher2.cs @@ -25,8 +25,10 @@ public partial struct AHasher2 : IHasher2 public static AHasherRandomState GlobalRandomState { [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - get; - } = GenerateRandomState(); + get => _globalRandomState; + } + + public static readonly AHasherRandomState _globalRandomState = GenerateRandomState(); [ThreadStatic] private static AHasherRandomState _threadCurrentRandomState; @@ -60,7 +62,7 @@ public static AHasherRandomState GenerateRandomState() var rand = Random.Shared; #endif Unsafe.SkipInit(out AHasherRandomState state); - rand.NextBytes(MemoryMarshal.Cast(state.AsSpan())); + rand.NextBytes(MemoryMarshal.Cast(state.UnsafeAsMutableSpan())); return state; } @@ -68,42 +70,39 @@ public static AHasherRandomState GenerateRandomState() #region Create - [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public static AHasher2 CreateGlobal() => new(GlobalRandomState); + public static AHasher2 Global + { + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + get; + } = new(GlobalRandomState); - [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public static AHasher2 CreateThreadCurrent() => new(ThreadCurrentRandomState); + public static AHasher2 ThreadCurrent + { + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + get; + } = new(ThreadCurrentRandomState); [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public static AHasher2 CreateRandom() => new(GenerateRandomState()); - + #endregion #region Impl -#if NET7_0_OR_GREATER - private Union union; - - [StructLayout(LayoutKind.Explicit)] - private struct Union - { - [FieldOffset(0)] - public AesHasher aesHasher; - [FieldOffset(0)] - public SoftHasher softHasher; - } + private AHasher2Data data; +#if NET7_0_OR_GREATER [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public AHasher2(AHasherRandomState randomState) { Unsafe.SkipInit(out this); if (AesHasher.IsSupported) { - union.aesHasher = new AesHasher(randomState); + data = AesHasher.Create(randomState); } else { - union.softHasher = new SoftHasher(randomState); + throw new NotImplementedException(); } } @@ -112,106 +111,100 @@ public AHasher2(AHasherRandomState randomState) [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public void Add(int value) { - if (AesHasher.IsSupported) union.aesHasher.Add(value); - else union.softHasher.Add(value); + if (AesHasher.IsSupported) AesHasher.Add(ref data, value); + else throw new NotImplementedException(); } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public void Add(long value) { - if (AesHasher.IsSupported) union.aesHasher.Add(value); - else union.softHasher.Add(value); + if (AesHasher.IsSupported) AesHasher.Add(ref data, value); + else throw new NotImplementedException(); } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public void Add(T value) { - if (AesHasher.IsSupported) union.aesHasher.Add(value); - else union.softHasher.Add(value); + if (AesHasher.IsSupported) AesHasher.Add(ref data, value); + else throw new NotImplementedException(); } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public void Add(T value, IEqualityComparer? comparer) { - if (AesHasher.IsSupported) union.aesHasher.Add(value, comparer); - else union.softHasher.Add(value, comparer); + if (AesHasher.IsSupported) AesHasher.Add(ref data, value, comparer); + else throw new NotImplementedException(); } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public void AddBytes(ReadOnlySpan value) { - if (AesHasher.IsSupported) union.aesHasher.AddBytes(value); - else union.softHasher.AddBytes(value); + if (AesHasher.IsSupported) AesHasher.AddBytes(ref data, value); + else throw new NotImplementedException(); } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public void AddString(ReadOnlySpan value) { - if (AesHasher.IsSupported) union.aesHasher.AddString(value); - else union.softHasher.AddString(value); + if (AesHasher.IsSupported) AesHasher.AddString(ref data, value); + else throw new NotImplementedException(); } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public void AddString(ReadOnlySpan value) { - if (AesHasher.IsSupported) union.aesHasher.AddString(value); - else union.softHasher.AddString(value); + if (AesHasher.IsSupported) AesHasher.AddString(ref data, value); + else throw new NotImplementedException(); } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public int ToHashCode() => AesHasher.IsSupported ? union.aesHasher.ToHashCode() : union.softHasher.ToHashCode(); + public int ToHashCode() => + AesHasher.IsSupported ? AesHasher.ToHashCode(ref data) : throw new NotImplementedException(); [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public long ToHashCodeLong() => - AesHasher.IsSupported ? union.aesHasher.ToHashCodeLong() : union.softHasher.ToHashCodeLong(); + AesHasher.IsSupported ? AesHasher.ToHashCodeLong(ref data) : throw new NotImplementedException(); #endregion #else private SoftHasher softHasher; - [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public AHasher2(SoftHasher softHasher) - { - Unsafe.SkipInit(out this); - this.softHasher = softHasher; - } - [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public AHasher2(AHasherRandomState randomState) { Unsafe.SkipInit(out this); - softHasher = new SoftHasher(randomState); + throw new NotImplementedException(); } #region IHasher [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public void Add(int value) => softHasher.Add(value); + public void Add(int value) => throw new NotImplementedException(); [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public void Add(long value) => softHasher.Add(value); + public void Add(long value) => throw new NotImplementedException(); [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public void Add(T value) => softHasher.Add(value); + public void Add(T value) => throw new NotImplementedException(); [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public void Add(T value, IEqualityComparer? comparer) => softHasher.Add(value, comparer); + public void Add(T value, IEqualityComparer? comparer) => throw new NotImplementedException(); [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public void AddBytes(ReadOnlySpan value) => softHasher.AddBytes(value); + public void AddBytes(ReadOnlySpan value) => throw new NotImplementedException(); [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public void AddString(ReadOnlySpan value) => softHasher.AddString(value); + public void AddString(ReadOnlySpan value) => throw new NotImplementedException(); [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public void AddString(ReadOnlySpan value) => softHasher.AddString(value); + public void AddString(ReadOnlySpan value) => throw new NotImplementedException(); [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public int ToHashCode() => softHasher.ToHashCode(); + public int ToHashCode() => throw new NotImplementedException(); [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public long ToHashCodeLong() => softHasher.ToHashCodeLong(); + public long ToHashCodeLong() => throw new NotImplementedException(); #endregion @@ -224,18 +217,25 @@ public AHasher2(AHasherRandomState randomState) [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public static int Combine(T1 value1) { - var hasher = CreateGlobal(); - hasher.Add(value1); - return hasher.ToHashCode(); +#if NET7_0_OR_GREATER + if (AesHasher.IsSupported) + { + return AesHasher.Combine(value1); + } +#endif + throw new NotImplementedException(); } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public static int Combine(T1 value1, T2 value2) { - var hasher = CreateGlobal(); - hasher.Add(value1); - hasher.Add(value2); - return hasher.ToHashCode(); +#if NET7_0_OR_GREATER + if (AesHasher.IsSupported) + { + return AesHasher.Combine(value1, value2); + } +#endif + throw new NotImplementedException(); } #endregion diff --git a/BetterCollections/Cryptography/AHasher/AHasherRandomState.cs b/BetterCollections/Cryptography/AHasher/AHasherRandomState.cs index 845ec2a..d7cae1d 100644 --- a/BetterCollections/Cryptography/AHasher/AHasherRandomState.cs +++ b/BetterCollections/Cryptography/AHasher/AHasherRandomState.cs @@ -9,80 +9,87 @@ namespace BetterCollections.Cryptography; -#if NET8_0_OR_GREATER -[InlineArray(4)] -[StructLayout(LayoutKind.Sequential, Size = sizeof(ulong) * 4)] -public struct AHasherKeys -{ - public ulong key; - - [UnscopedRef] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span AsSpan() => MemoryMarshal.CreateSpan(ref key, 4); -} -#endif - -[StructLayout(LayoutKind.Explicit)] -public struct AHasherRandomState +public readonly struct AHasherRandomState { - [FieldOffset(0)] - public ulong a; - [FieldOffset(sizeof(ulong))] - public ulong b; - [FieldOffset(sizeof(ulong) * 2)] - public ulong c; - [FieldOffset(sizeof(ulong) * 3)] - public ulong d; +#if NET7_0_OR_GREATER + public readonly Vector128 key1; + public readonly Vector128 key2; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public AHasherRandomState(ulong a, ulong b, ulong c, ulong d) + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public AHasherRandomState(Vector128 key1, Vector128 key2) { Unsafe.SkipInit(out this); - this.a = a; - this.b = b; - this.c = c; - this.d = d; + this.key1 = key1; + this.key2 = key2; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [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); - a = keys[0]; - b = keys[1]; - c = keys[2]; - d = keys[3]; +#if NET8_0_OR_GREATER + key1 = Vector128.LoadUnsafe(in keys[0]).AsByte(); + key2 = Vector128.LoadUnsafe(in keys[2]).AsByte(); +#else + key1 = Vector128.LoadUnsafe(ref Unsafe.AsRef(in keys[0])).AsByte(); + key2 = Vector128.LoadUnsafe(ref Unsafe.AsRef(in keys[2])).AsByte(); +#endif + } + + [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; } -#if NET8_0_OR_GREATER - [FieldOffset(0)] - public AHasherKeys keys; + #region Getters - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public AHasherRandomState(AHasherKeys keys) + [UnscopedRef] + public ref readonly ulong a { - Unsafe.SkipInit(out this); - this.keys = keys; + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + get => ref Unsafe.As(ref Unsafe.AsRef(in this)); } -#endif -#if NET6_0_OR_GREATER - [FieldOffset(0)] - public Vector128 key1; - [FieldOffset(sizeof(ulong) * 2)] - public Vector128 key2; + [UnscopedRef] + public ref readonly ulong b + { + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + get => ref Unsafe.Add(ref Unsafe.As(ref Unsafe.AsRef(in this)), 1); + } + + #endregion - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public AHasherRandomState(Vector128 key1, Vector128 key2) +#else + public readonly ulong a; + public readonly ulong b; + + [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); - this.key1 = key1; - this.key2 = key2; + a = keys[0]; + b = keys[1]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public AHasherRandomState(ulong a, ulong b) + { + Unsafe.SkipInit(out this); + this.a = a; + this.b = b; } #endif - + + [UnscopedRef] + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public ReadOnlySpan AsSpan() => MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in a), 4); + [UnscopedRef] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span AsSpan() => MemoryMarshal.CreateSpan(ref a, 4); + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public Span UnsafeAsMutableSpan() => MemoryMarshal.CreateSpan(ref Unsafe.AsRef(in a), 4); } diff --git a/BetterCollections/Cryptography/AHasher/AesHasher.cs b/BetterCollections/Cryptography/AHasher/AesHasher.cs index 732b322..27e7f96 100644 --- a/BetterCollections/Cryptography/AHasher/AesHasher.cs +++ b/BetterCollections/Cryptography/AHasher/AesHasher.cs @@ -10,8 +10,7 @@ namespace BetterCollections.Cryptography.AHasherImpl; -[method: MethodImpl(MethodImplOptions.AggressiveInlining)] -public struct AesHasher(AHasherRandomState randomState) : IHasher2 +public static class AesHasher { public static bool IsSupported { @@ -22,74 +21,88 @@ public static bool IsSupported [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public static AHasher2Data Create(AHasherRandomState randomState) => new(randomState.key1, randomState.key2, randomState.key1 ^ randomState.key2); - - private Vector128 enc = randomState.key1; - private Vector128 sum = randomState.key2; - private readonly Vector128 key = randomState.key1 ^ randomState.key2; [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public void Add(int value) => Add((long)value); + public static Vector128 ToVector(T value) + { + var type = typeof(T); + if (type == typeof(int)) return Vector128.CreateScalar(Unsafe.As(ref value)).AsByte(); + else if (type == typeof(uint)) return Vector128.CreateScalar(Unsafe.As(ref value)).AsByte(); + else if (type == typeof(long)) return Vector128.CreateScalar(Unsafe.As(ref value)).AsByte(); + else if (type == typeof(ulong)) return Vector128.CreateScalar(Unsafe.As(ref value)).AsByte(); + else if (type == typeof(Int128)) return Unsafe.As>(ref value); + else if (type == typeof(UInt128)) return Unsafe.As>(ref value); + else if (type == typeof(Vector128)) return Unsafe.As>(ref value); + else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Vector128<>)) + return Unsafe.As>(ref value); + else return Vector128.CreateScalar(value == null ? 0 : value.GetHashCode()).AsByte(); + } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public void Add(uint value) => Add((ulong)value); + public static void Add(ref AHasher2Data data, int value) => Add(ref data, (long)value); [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public void Add(long value) => Add(Vector128.CreateScalar(value)); + public static void Add(ref AHasher2Data data, uint value) => Add(ref data, (ulong)value); [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public void Add(ulong value) => Add(Vector128.CreateScalar(value)); + public static void Add(ref AHasher2Data data, long value) => Add(ref data, Vector128.CreateScalar(value)); [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public void Add(Int128 value) => Add(Unsafe.As>(ref value)); + public static void Add(ref AHasher2Data data, ulong value) => Add(ref data, Vector128.CreateScalar(value)); [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public void Add(UInt128 value) => Add(Unsafe.As>(ref value)); + public static void Add(ref AHasher2Data data, Int128 value) => + Add(ref data, Unsafe.As>(ref value)); [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public void Add(T value) + public static void Add(ref AHasher2Data data, UInt128 value) => + Add(ref data, Unsafe.As>(ref value)); + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public static void Add(ref AHasher2Data data, T value) { var type = typeof(T); - if (type == typeof(int)) Add(Unsafe.As(ref value)); - else if (type == typeof(uint)) Add(Unsafe.As(ref value)); - else if (type == typeof(long)) Add(Unsafe.As(ref value)); - else if (type == typeof(ulong)) Add(Unsafe.As(ref value)); - else if (type == typeof(Int128)) Add(Unsafe.As(ref value)); - else if (type == typeof(UInt128)) Add(Unsafe.As(ref value)); - else if (type == typeof(Vector128)) Add(Unsafe.As>(ref value)); + if (type == typeof(int)) Add(ref data, Unsafe.As(ref value)); + else if (type == typeof(uint)) Add(ref data, Unsafe.As(ref value)); + else if (type == typeof(long)) Add(ref data, Unsafe.As(ref value)); + else if (type == typeof(ulong)) Add(ref data, Unsafe.As(ref value)); + else if (type == typeof(Int128)) Add(ref data, Unsafe.As(ref value)); + else if (type == typeof(UInt128)) Add(ref data, Unsafe.As(ref value)); + else if (type == typeof(Vector128)) Add(ref data, Unsafe.As>(ref value)); else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Vector128<>)) - Add(Unsafe.As>(ref value)); - else Add(value == null ? 0 : value.GetHashCode()); + Add(ref data, Unsafe.As>(ref value)); + else Add(ref data, value == null ? 0 : value.GetHashCode()); } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public void Add(T value, IEqualityComparer? comparer) + public static void Add(ref AHasher2Data data, T value, IEqualityComparer? comparer) { - if (comparer == null) Add(value); - else Add(value == null ? 0 : comparer.GetHashCode(value)); + if (comparer == null) Add(ref data, value); + else Add(ref data, value == null ? 0 : comparer.GetHashCode(value)); } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public void Add(Vector128 value) + public static void Add(ref AHasher2Data data, Vector128 value) #if NET7_0 where T : struct #endif - => Add(value.AsByte()); + => Add(ref data, value.AsByte()); [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public void Add(Vector128 value) + public static void Add(ref AHasher2Data data, Vector128 value) { - enc = AesDec(enc, value); - sum = ShuffleAndAdd(sum, value); + data.enc = AesDec(data.enc, value); + data.sum = ShuffleAndAdd(data.sum, value); } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public void AddBytes(ReadOnlySpan value) + public static void AddBytes(ref AHasher2Data data, ReadOnlySpan value) { - AddLength(value.Length); + AddLength(ref data, value.Length); if (value.Length <= 8) { - Add(ReadSmall(value)); + Add(ref data, ReadSmall(value)); } else if (value.Length > 32) { @@ -111,12 +124,13 @@ public void AddBytes(ReadOnlySpan value) Vector128.LoadUnsafe(ref Unsafe.AsRef(in value[^(sizeof(ulong) * 8)])), }; #endif - Span> current = stackalloc Vector128[4] { key, key, key, key }; + Span> current = stackalloc Vector128[4] + { data.key, data.key, data.key, data.key }; current[0] = AesEnc(current[0], tail[0]); current[1] = AesEnc(current[1], tail[1]); current[2] = AesEnc(current[2], tail[2]); current[3] = AesEnc(current[3], tail[3]); - Span> sum = stackalloc Vector128[2] { key, ~key }; + Span> sum = stackalloc Vector128[2] { data.key, ~data.key }; sum[0] = AddByU64(sum[0], tail[0]); sum[1] = AddByU64(sum[1], tail[1]); sum[0] = ShuffleAndAdd(sum[0], tail[2]); @@ -148,12 +162,12 @@ public void AddBytes(ReadOnlySpan value) sum[1] = ShuffleAndAdd(sum[1], blocks[3]); } - Add(current[0]); - Add(current[1]); - Add(current[2]); - Add(current[3]); - Add(sum[0]); - Add(sum[1]); + Add(ref data, current[0]); + Add(ref data, current[1]); + Add(ref data, current[2]); + Add(ref data, current[3]); + Add(ref data, sum[0]); + Add(ref data, sum[1]); } else // 33 .. 64 { @@ -168,10 +182,10 @@ public void AddBytes(ReadOnlySpan value) var c = Vector128.LoadUnsafe(ref Unsafe.AsRef(in value[^(sizeof(ulong) * 4)])); var d = Vector128.LoadUnsafe(ref Unsafe.AsRef(in value[^(sizeof(ulong) * 2)])); #endif - Add(a); - Add(b); - Add(c); - Add(d); + Add(ref data, a); + Add(ref data, b); + Add(ref data, c); + Add(ref data, d); } } else if (value.Length > 16) // 17 .. 32 @@ -183,58 +197,59 @@ public void AddBytes(ReadOnlySpan value) var a = Vector128.LoadUnsafe(ref Unsafe.AsRef(in value[0])); var b = Vector128.LoadUnsafe(ref Unsafe.AsRef(in value[^(sizeof(ulong) * 2)])); #endif - Add(a); - Add(b); + Add(ref data, a); + Add(ref data, b); } else // 9 .. 16 { var a = Vector128.Create(MemoryMarshal.Cast(value)[0], MemoryMarshal.Cast(value.Slice(value.Length - sizeof(ulong), sizeof(ulong)))[0]); - Add(a); + Add(ref data, a); } } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public void AddString(ReadOnlySpan value) + public static void AddString(ref AHasher2Data data, ReadOnlySpan value) { if (value.Length > 8) { - AddBytes(value); - enc = AesEnc(sum, enc); - enc = AesDec(AesDec(enc, key), enc); + AddBytes(ref data, value); + data.enc = AesEnc(data.sum, data.enc); + data.enc = AesDec(AesDec(data.enc, data.key), data.enc); } else { - AddLength(value.Length); + AddLength(ref data, value.Length); var a = ReadSmall(value); - sum = ShuffleAndAdd(sum, a); - enc = AesEnc(sum, enc); - enc = AesDec(AesDec(enc, key), enc); + data.sum = ShuffleAndAdd(data.sum, a); + data.enc = AesEnc(data.sum, data.enc); + data.enc = AesDec(AesDec(data.enc, data.key), data.enc); } } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public void AddString(ReadOnlySpan value) => AddString(MemoryMarshal.Cast(value)); + public static void AddString(ref AHasher2Data data, ReadOnlySpan value) => + AddString(ref data, MemoryMarshal.Cast(value)); [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public int ToHashCode() + public static int ToHashCode(ref AHasher2Data data) { - var combined = AesEnc(sum, enc); - var result = AesDec(AesDec(combined, key), combined).AsInt32(); + var combined = AesEnc(data.sum, data.enc); + var result = AesDec(AesDec(combined, data.key), combined).AsInt32(); return result[0]; } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public long ToHashCodeLong() + public static long ToHashCodeLong(ref AHasher2Data data) { - var combined = AesEnc(sum, enc); - var result = AesDec(AesDec(combined, key), combined).AsInt64(); + var combined = AesEnc(data.sum, data.enc); + var result = AesDec(AesDec(combined, data.key), combined).AsInt64(); return result[0]; } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - private Vector128 ReadSmall(ReadOnlySpan value) + private static Vector128 ReadSmall(ReadOnlySpan value) { Debug.Assert(value.Length is < 8 and >= 0); return value.Length switch @@ -249,9 +264,9 @@ private Vector128 ReadSmall(ReadOnlySpan value) } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - private void AddLength(int len) + private static void AddLength(ref AHasher2Data data, int len) { - enc = (enc.AsInt32() + Vector128.CreateScalar(len)).AsByte(); + data.enc = (data.enc.AsInt32() + Vector128.CreateScalar(len)).AsByte(); } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] @@ -300,6 +315,37 @@ private static Vector128 AddByU64(Vector128 a, Vector128 b) => 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(AHasher2._globalRandomState); + var target1 = ToVector(value1); + data.enc = AesDec(data.enc, target1); + data.sum = ShuffleAndAdd(data.sum, target1); + 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 int Combine(T1 value1, T2 value2) + { + var data = Create(AHasher2._globalRandomState); + var target1 = ToVector(value1); + data.enc = AesDec(data.enc, target1); + data.sum = ShuffleAndAdd(data.sum, target1); + var target2 = ToVector(value2); + data.enc = AesDec(data.enc, target2); + data.sum = ShuffleAndAdd(data.sum, target2); + var combined = AesEnc(data.sum, data.enc); + var result = AesDec(AesDec(combined, data.key), combined).AsInt32(); + return result[0]; + } + + #endregion } #endif diff --git a/BetterCollections/Cryptography/AHasher/SoftHasher.cs b/BetterCollections/Cryptography/AHasher/SoftHasher.cs index f8e10a5..ec32898 100644 --- a/BetterCollections/Cryptography/AHasher/SoftHasher.cs +++ b/BetterCollections/Cryptography/AHasher/SoftHasher.cs @@ -5,59 +5,7 @@ namespace BetterCollections.Cryptography.AHasherImpl; -public struct SoftHasher(AHasherRandomState randomState) : IHasher2 +public struct SoftHasher { - public ulong buffer = randomState.a; - public ulong pad = randomState.b; - - [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public void Add(int value) - { - throw new NotImplementedException(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public void Add(long value) - { - throw new NotImplementedException(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public void Add(T value) - { - throw new NotImplementedException(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public void Add(T value, IEqualityComparer? comparer) - { - throw new NotImplementedException(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public void AddBytes(ReadOnlySpan value) - { - throw new NotImplementedException(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public void AddString(ReadOnlySpan value) - { - throw new NotImplementedException(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public void AddString(ReadOnlySpan value) => AddString(MemoryMarshal.Cast(value)); - - [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public int ToHashCode() - { - throw new NotImplementedException(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public long ToHashCodeLong() - { - throw new NotImplementedException(); - } + } From 85b9ca620e05989518da36a0ede751ab42f7c2a0 Mon Sep 17 00:00:00 2001 From: U2A5F Date: Thu, 28 Dec 2023 18:59:43 +0800 Subject: [PATCH 3/7] save --- Benchmark/BenchmarkAHash.cs | 10 +- .../Cryptography/AHasher/AHasher2.cs | 84 ++---- .../Cryptography/AHasher/AHasher2Data.cs | 14 +- .../AHasher/AHasherRandomState.cs | 42 +-- .../Cryptography/AHasher/AesHasher.cs | 270 ++++++++++++------ .../Cryptography/AHasher/SoftHasher.cs | 11 +- 6 files changed, 225 insertions(+), 206 deletions(-) diff --git a/Benchmark/BenchmarkAHash.cs b/Benchmark/BenchmarkAHash.cs index 64e2d13..960b337 100644 --- a/Benchmark/BenchmarkAHash.cs +++ b/Benchmark/BenchmarkAHash.cs @@ -1,7 +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; @@ -23,6 +25,7 @@ public ulong AHash() return r; } +#if NET8_0_OR_GREATER [Benchmark] public int AHash2() { @@ -34,8 +37,6 @@ public int AHash2() return r; } -#if NET7_0_OR_GREATER - [Benchmark] public int AHash3() { @@ -43,12 +44,11 @@ public int AHash3() for (int i = 0; i < 1000; i++) { var hasher = AesHasher.Create(AHasher2.GlobalRandomState); - AesHasher.Add(ref hasher, i); - r += AesHasher.ToHashCode(ref hasher); + hasher = AesHasher.Add(hasher, i); + r += AesHasher.ToHashCode(hasher); } return r; } - #endif [Benchmark(Baseline = true)] diff --git a/BetterCollections/Cryptography/AHasher/AHasher2.cs b/BetterCollections/Cryptography/AHasher/AHasher2.cs index dcaeaae..d33928a 100644 --- a/BetterCollections/Cryptography/AHasher/AHasher2.cs +++ b/BetterCollections/Cryptography/AHasher/AHasher2.cs @@ -1,14 +1,13 @@ -using System; +#if NET8_0_OR_GREATER +using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using BetterCollections.Cryptography.AHasherImpl; - -#if NET7_0_OR_GREATER using System.Runtime.Intrinsics; using X86 = System.Runtime.Intrinsics.X86; using Arm = System.Runtime.Intrinsics.Arm; -#endif + namespace BetterCollections.Cryptography; @@ -18,7 +17,7 @@ namespace BetterCollections.Cryptography; /// A hasher that ensures even distribution of each bit /// If possible use Aes SIMD acceleration (.net7+) /// -public partial struct AHasher2 : IHasher2 +public struct AHasher2 : IHasher2 { #region GlobalRandomState @@ -56,11 +55,7 @@ public static AHasherRandomState ThreadCurrentRandomState [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public static AHasherRandomState GenerateRandomState() { -#if NETSTANDARD - var rand = new Random(); -#else var rand = Random.Shared; -#endif Unsafe.SkipInit(out AHasherRandomState state); rand.NextBytes(MemoryMarshal.Cast(state.UnsafeAsMutableSpan())); return state; @@ -68,6 +63,7 @@ public static AHasherRandomState GenerateRandomState() #endregion + #region Create public static AHasher2 Global @@ -84,14 +80,13 @@ public static AHasher2 ThreadCurrent [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public static AHasher2 CreateRandom() => new(GenerateRandomState()); - + #endregion #region Impl private AHasher2Data data; -#if NET7_0_OR_GREATER [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public AHasher2(AHasherRandomState randomState) { @@ -111,105 +106,62 @@ public AHasher2(AHasherRandomState randomState) [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public void Add(int value) { - if (AesHasher.IsSupported) AesHasher.Add(ref data, value); + if (AesHasher.IsSupported) data = AesHasher.Add(data, value); else throw new NotImplementedException(); } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public void Add(long value) { - if (AesHasher.IsSupported) AesHasher.Add(ref data, value); + if (AesHasher.IsSupported) data = AesHasher.Add(data, value); else throw new NotImplementedException(); } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public void Add(T value) { - if (AesHasher.IsSupported) AesHasher.Add(ref data, value); + if (AesHasher.IsSupported) data = AesHasher.Add(data, value); else throw new NotImplementedException(); } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public void Add(T value, IEqualityComparer? comparer) { - if (AesHasher.IsSupported) AesHasher.Add(ref data, value, comparer); + if (AesHasher.IsSupported) data = AesHasher.Add(data, value, comparer); else throw new NotImplementedException(); } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public void AddBytes(ReadOnlySpan value) { - if (AesHasher.IsSupported) AesHasher.AddBytes(ref data, value); + if (AesHasher.IsSupported) data = AesHasher.AddBytes(data, value); else throw new NotImplementedException(); } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public void AddString(ReadOnlySpan value) { - if (AesHasher.IsSupported) AesHasher.AddString(ref data, value); + if (AesHasher.IsSupported) data = AesHasher.AddString(data, value); else throw new NotImplementedException(); } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public void AddString(ReadOnlySpan value) { - if (AesHasher.IsSupported) AesHasher.AddString(ref data, value); + if (AesHasher.IsSupported) data = AesHasher.AddString(data, value); else throw new NotImplementedException(); } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public int ToHashCode() => - AesHasher.IsSupported ? AesHasher.ToHashCode(ref data) : throw new NotImplementedException(); + AesHasher.IsSupported ? AesHasher.ToHashCode(data) : throw new NotImplementedException(); [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public long ToHashCodeLong() => - AesHasher.IsSupported ? AesHasher.ToHashCodeLong(ref data) : throw new NotImplementedException(); - - #endregion - -#else - private SoftHasher softHasher; - - [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public AHasher2(AHasherRandomState randomState) - { - Unsafe.SkipInit(out this); - throw new NotImplementedException(); - } - - #region IHasher - - [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public void Add(int value) => throw new NotImplementedException(); - - [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public void Add(long value) => throw new NotImplementedException(); - - [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public void Add(T value) => throw new NotImplementedException(); - - [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public void Add(T value, IEqualityComparer? comparer) => throw new NotImplementedException(); - - [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public void AddBytes(ReadOnlySpan value) => throw new NotImplementedException(); - - [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public void AddString(ReadOnlySpan value) => throw new NotImplementedException(); - - [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public void AddString(ReadOnlySpan value) => throw new NotImplementedException(); - - [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public int ToHashCode() => throw new NotImplementedException(); - - [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public long ToHashCodeLong() => throw new NotImplementedException(); + AesHasher.IsSupported ? AesHasher.ToHashCodeLong(data) : throw new NotImplementedException(); #endregion -#endif - #endregion #region Combine @@ -217,26 +169,24 @@ public AHasher2(AHasherRandomState randomState) [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public static int Combine(T1 value1) { -#if NET7_0_OR_GREATER if (AesHasher.IsSupported) { return AesHasher.Combine(value1); } -#endif throw new NotImplementedException(); } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public static int Combine(T1 value1, T2 value2) { -#if NET7_0_OR_GREATER if (AesHasher.IsSupported) { return AesHasher.Combine(value1, value2); } -#endif throw new NotImplementedException(); } #endregion } + +#endif diff --git a/BetterCollections/Cryptography/AHasher/AHasher2Data.cs b/BetterCollections/Cryptography/AHasher/AHasher2Data.cs index a9cc5f6..c6be17e 100644 --- a/BetterCollections/Cryptography/AHasher/AHasher2Data.cs +++ b/BetterCollections/Cryptography/AHasher/AHasher2Data.cs @@ -1,14 +1,12 @@ -using System.Diagnostics.CodeAnalysis; +#if NET8_0_OR_GREATER +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; -#if NET7_0_OR_GREATER using System.Runtime.Intrinsics; -#endif namespace BetterCollections.Cryptography.AHasherImpl; public struct AHasher2Data { -#if NET7_0_OR_GREATER public Vector128 enc; public Vector128 sum; public readonly Vector128 key; @@ -28,7 +26,7 @@ public AHasher2Data(Vector128 enc) Unsafe.SkipInit(out this); this.enc = enc; } - + [UnscopedRef] public ref ulong buffer { @@ -42,10 +40,6 @@ public ref ulong pad [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] get => ref Unsafe.Add(ref Unsafe.As(ref this), 1); } -#else - public ulong buffer; - public ulong pad; -#endif [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public AHasher2Data(ulong buffer, ulong pad) @@ -55,3 +49,5 @@ public AHasher2Data(ulong buffer, ulong pad) this.pad = pad; } } + +#endif diff --git a/BetterCollections/Cryptography/AHasher/AHasherRandomState.cs b/BetterCollections/Cryptography/AHasher/AHasherRandomState.cs index d7cae1d..fdc1315 100644 --- a/BetterCollections/Cryptography/AHasher/AHasherRandomState.cs +++ b/BetterCollections/Cryptography/AHasher/AHasherRandomState.cs @@ -1,17 +1,14 @@ -using System; +#if NET8_0_OR_GREATER +using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - -#if NET6_0_OR_GREATER using System.Runtime.Intrinsics; -#endif namespace BetterCollections.Cryptography; public readonly struct AHasherRandomState { -#if NET7_0_OR_GREATER public readonly Vector128 key1; public readonly Vector128 key2; @@ -28,15 +25,10 @@ public AHasherRandomState(ReadOnlySpan keys) { if (keys.Length < 4) throw new ArgumentOutOfRangeException(nameof(keys), "length of keys must >= 4"); Unsafe.SkipInit(out this); -#if NET8_0_OR_GREATER key1 = Vector128.LoadUnsafe(in keys[0]).AsByte(); key2 = Vector128.LoadUnsafe(in keys[2]).AsByte(); -#else - key1 = Vector128.LoadUnsafe(ref Unsafe.AsRef(in keys[0])).AsByte(); - key2 = Vector128.LoadUnsafe(ref Unsafe.AsRef(in keys[2])).AsByte(); -#endif } - + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public AHasherRandomState(ulong a, ulong b) { @@ -60,36 +52,16 @@ public ref readonly ulong b [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] get => ref Unsafe.Add(ref Unsafe.As(ref Unsafe.AsRef(in this)), 1); } - - #endregion -#else - public readonly ulong a; - public readonly ulong b; + #endregion - [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); - a = keys[0]; - b = keys[1]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public AHasherRandomState(ulong a, ulong b) - { - Unsafe.SkipInit(out this); - this.a = a; - this.b = b; - } -#endif - [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 index 27e7f96..a5bdb97 100644 --- a/BetterCollections/Cryptography/AHasher/AesHasher.cs +++ b/BetterCollections/Cryptography/AHasher/AesHasher.cs @@ -1,4 +1,4 @@ -#if NET7_0_OR_GREATER +#if NET8_0_OR_GREATER using System; using System.Collections.Generic; using System.Diagnostics; @@ -23,86 +23,114 @@ public static AHasher2Data Create(AHasherRandomState randomState) => new(randomState.key1, randomState.key2, randomState.key1 ^ randomState.key2); [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public static Vector128 ToVector(T value) - { - var type = typeof(T); - if (type == typeof(int)) return Vector128.CreateScalar(Unsafe.As(ref value)).AsByte(); - else if (type == typeof(uint)) return Vector128.CreateScalar(Unsafe.As(ref value)).AsByte(); - else if (type == typeof(long)) return Vector128.CreateScalar(Unsafe.As(ref value)).AsByte(); - else if (type == typeof(ulong)) return Vector128.CreateScalar(Unsafe.As(ref value)).AsByte(); - else if (type == typeof(Int128)) return Unsafe.As>(ref value); - else if (type == typeof(UInt128)) return Unsafe.As>(ref value); - else if (type == typeof(Vector128)) return Unsafe.As>(ref value); - else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Vector128<>)) - return Unsafe.As>(ref value); - else return Vector128.CreateScalar(value == null ? 0 : value.GetHashCode()).AsByte(); - } + 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 void Add(ref AHasher2Data data, int value) => Add(ref data, (long)value); + public static AHasher2Data Add(AHasher2Data data, uint value) => + Add(data, Vector128.CreateScalar(value)); [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public static void Add(ref AHasher2Data data, uint value) => Add(ref data, (ulong)value); + public static AHasher2Data Add(AHasher2Data data, long value) => + Add(data, Vector128.CreateScalar(value)); [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public static void Add(ref AHasher2Data data, long value) => Add(ref data, Vector128.CreateScalar(value)); + public static AHasher2Data Add(AHasher2Data data, ulong value) => + Add(data, Vector128.CreateScalar(value)); [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public static void Add(ref AHasher2Data data, ulong value) => Add(ref data, Vector128.CreateScalar(value)); + public static AHasher2Data Add(AHasher2Data data, nint value) => + Add(data, Vector128.CreateScalar(value)); [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public static void Add(ref AHasher2Data data, Int128 value) => - Add(ref data, Unsafe.As>(ref value)); + public static AHasher2Data Add(AHasher2Data data, nuint value) => + Add(data, Vector128.CreateScalar(value)); [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public static void Add(ref AHasher2Data data, UInt128 value) => - Add(ref data, Unsafe.As>(ref value)); + public static AHasher2Data Add(AHasher2Data data, float value) => + Add(data, Vector128.CreateScalar(value)); [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public static void Add(ref AHasher2Data data, T value) + 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) { - var type = typeof(T); - if (type == typeof(int)) Add(ref data, Unsafe.As(ref value)); - else if (type == typeof(uint)) Add(ref data, Unsafe.As(ref value)); - else if (type == typeof(long)) Add(ref data, Unsafe.As(ref value)); - else if (type == typeof(ulong)) Add(ref data, Unsafe.As(ref value)); - else if (type == typeof(Int128)) Add(ref data, Unsafe.As(ref value)); - else if (type == typeof(UInt128)) Add(ref data, Unsafe.As(ref value)); - else if (type == typeof(Vector128)) Add(ref data, Unsafe.As>(ref value)); - else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Vector128<>)) - Add(ref data, Unsafe.As>(ref value)); - else Add(ref data, value == null ? 0 : value.GetHashCode()); + 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)); + + if (typeof(T) == typeof(string)) + return value == null ? Add(data, 0) : AddString(data, ((string)(object)value).AsSpan()); + if (typeof(T) == typeof(Memory)) + return AddString(data, ((Memory)(object)value!).Span); + if (typeof(T) == typeof(ReadOnlyMemory)) + return AddString(data, ((Memory)(object)value!).Span); + if (typeof(T) == typeof(Memory)) + return AddBytes(data, ((Memory)(object)value!).Span); + if (typeof(T) == typeof(ReadOnlyMemory)) + return AddBytes(data, ((Memory)(object)value!).Span); + + return Add(data, Vector128.CreateScalar(value == null ? 0 : value.GetHashCode()).AsByte()); } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public static void Add(ref AHasher2Data data, T value, IEqualityComparer? comparer) + public static AHasher2Data Add(AHasher2Data data, T value, IEqualityComparer? comparer) { - if (comparer == null) Add(ref data, value); - else Add(ref data, value == null ? 0 : comparer.GetHashCode(value)); + if (comparer == null) return Add(data, value); + else return Add(data, value == null ? 0 : comparer.GetHashCode(value)); } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public static void Add(ref AHasher2Data data, Vector128 value) + public static AHasher2Data Add(AHasher2Data data, Vector128 value) #if NET7_0 where T : struct #endif - => Add(ref data, value.AsByte()); + => Add(data, value.AsByte()); [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public static void Add(ref AHasher2Data data, Vector128 value) + 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 void AddBytes(ref AHasher2Data data, ReadOnlySpan value) + public static AHasher2Data AddBytes(AHasher2Data data, ReadOnlySpan value) { - AddLength(ref data, value.Length); + data = AddLength(data, value.Length); if (value.Length <= 8) { - Add(ref data, ReadSmall(value)); + data = Add(data, ReadSmall(value)); } else if (value.Length > 32) { @@ -162,12 +190,12 @@ public static void AddBytes(ref AHasher2Data data, ReadOnlySpan value) sum[1] = ShuffleAndAdd(sum[1], blocks[3]); } - Add(ref data, current[0]); - Add(ref data, current[1]); - Add(ref data, current[2]); - Add(ref data, current[3]); - Add(ref data, sum[0]); - Add(ref data, sum[1]); + data = Add(data, current[0]); + data = Add(data, current[1]); + data = Add(data, current[2]); + data = Add(data, current[3]); + data = Add(data, sum[0]); + data = Add(data, sum[1]); } else // 33 .. 64 { @@ -182,10 +210,10 @@ public static void AddBytes(ref AHasher2Data data, ReadOnlySpan value) var c = Vector128.LoadUnsafe(ref Unsafe.AsRef(in value[^(sizeof(ulong) * 4)])); var d = Vector128.LoadUnsafe(ref Unsafe.AsRef(in value[^(sizeof(ulong) * 2)])); #endif - Add(ref data, a); - Add(ref data, b); - Add(ref data, c); - Add(ref data, d); + data = Add(data, a); + data = Add(data, b); + data = Add(data, c); + data = Add(data, d); } } else if (value.Length > 16) // 17 .. 32 @@ -197,43 +225,46 @@ public static void AddBytes(ref AHasher2Data data, ReadOnlySpan value) var a = Vector128.LoadUnsafe(ref Unsafe.AsRef(in value[0])); var b = Vector128.LoadUnsafe(ref Unsafe.AsRef(in value[^(sizeof(ulong) * 2)])); #endif - Add(ref data, a); - Add(ref data, b); + data = Add(data, a); + data = Add(data, b); } else // 9 .. 16 { var a = Vector128.Create(MemoryMarshal.Cast(value)[0], MemoryMarshal.Cast(value.Slice(value.Length - sizeof(ulong), sizeof(ulong)))[0]); - Add(ref data, a); + data = Add(data, a); } + + return data; } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public static void AddString(ref AHasher2Data data, ReadOnlySpan value) + public static AHasher2Data AddString(AHasher2Data data, ReadOnlySpan value) { if (value.Length > 8) { - AddBytes(ref data, value); + data = AddBytes(data, value); data.enc = AesEnc(data.sum, data.enc); data.enc = AesDec(AesDec(data.enc, data.key), data.enc); } else { - AddLength(ref data, value.Length); + 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 void AddString(ref AHasher2Data data, ReadOnlySpan value) => - AddString(ref data, MemoryMarshal.Cast(value)); + public static AHasher2Data AddString(AHasher2Data data, ReadOnlySpan value) => + AddString(data, MemoryMarshal.Cast(value)); [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public static int ToHashCode(ref AHasher2Data data) + public static int ToHashCode(AHasher2Data data) { var combined = AesEnc(data.sum, data.enc); var result = AesDec(AesDec(combined, data.key), combined).AsInt32(); @@ -241,13 +272,21 @@ public static int ToHashCode(ref AHasher2Data data) } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public static long ToHashCodeLong(ref AHasher2Data data) + 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) { @@ -263,12 +302,6 @@ private static Vector128 ReadSmall(ReadOnlySpan value) }; } - [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - private static void AddLength(ref AHasher2Data data, int len) - { - data.enc = (data.enc.AsInt32() + Vector128.CreateScalar(len)).AsByte(); - } - [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] private static Vector128 AesDec(Vector128 value, Vector128 xor) { @@ -322,27 +355,94 @@ private static Vector128 AddByU64(Vector128 a, Vector128 b) => public static int Combine(T1 value1) { var data = Create(AHasher2._globalRandomState); - var target1 = ToVector(value1); - data.enc = AesDec(data.enc, target1); - data.sum = ShuffleAndAdd(data.sum, target1); - var combined = AesEnc(data.sum, data.enc); - var result = AesDec(AesDec(combined, data.key), combined).AsInt32(); - return result[0]; + data = Add(data, value1); + return ToHashCode(data); } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public static int Combine(T1 value1, T2 value2) { var data = Create(AHasher2._globalRandomState); - var target1 = ToVector(value1); - data.enc = AesDec(data.enc, target1); - data.sum = ShuffleAndAdd(data.sum, target1); - var target2 = ToVector(value2); - data.enc = AesDec(data.enc, target2); - data.sum = ShuffleAndAdd(data.sum, target2); - var combined = AesEnc(data.sum, data.enc); - var result = AesDec(AesDec(combined, data.key), combined).AsInt32(); - return result[0]; + 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(AHasher2._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(AHasher2._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(AHasher2._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(AHasher2._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(AHasher2._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(AHasher2._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 diff --git a/BetterCollections/Cryptography/AHasher/SoftHasher.cs b/BetterCollections/Cryptography/AHasher/SoftHasher.cs index ec32898..832af86 100644 --- a/BetterCollections/Cryptography/AHasher/SoftHasher.cs +++ b/BetterCollections/Cryptography/AHasher/SoftHasher.cs @@ -1,11 +1,12 @@ -using System; +#if NET8_0_OR_GREATER + +using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace BetterCollections.Cryptography.AHasherImpl; -public struct SoftHasher -{ - -} +public struct SoftHasher { } + +#endif From 6cba87771b87d5de69f9532257d3ba876ef2362f Mon Sep 17 00:00:00 2001 From: U2A5F Date: Thu, 28 Dec 2023 22:02:29 +0800 Subject: [PATCH 4/7] save --- Benchmark/BenchmarkAHashString.cs | 45 ++++ .../Cryptography/AHasher/AesHasher.cs | 237 +++++++++--------- 2 files changed, 167 insertions(+), 115 deletions(-) create mode 100644 Benchmark/BenchmarkAHashString.cs diff --git a/Benchmark/BenchmarkAHashString.cs b/Benchmark/BenchmarkAHashString.cs new file mode 100644 index 0000000..927d2d1 --- /dev/null +++ b/Benchmark/BenchmarkAHashString.cs @@ -0,0 +1,45 @@ +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 = AHasher2.Global; + hasher.AddString(Str); + return hasher.ToHashCode(); + } + + [Benchmark] + public int AHash_AddString_2() + { + var hasher = AesHasher.Create(AHasher2.GlobalRandomState); + hasher = AesHasher.AddString(hasher, Str); + return AesHasher.ToHashCode(hasher); + } +#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/AesHasher.cs b/BetterCollections/Cryptography/AHasher/AesHasher.cs index a5bdb97..8750fec 100644 --- a/BetterCollections/Cryptography/AHasher/AesHasher.cs +++ b/BetterCollections/Cryptography/AHasher/AesHasher.cs @@ -87,18 +87,7 @@ public static AHasher2Data Add(AHasher2Data data, T value) if (typeof(T) == typeof(Int128) || typeof(T) == typeof(UInt128) || typeof(T) == typeof(Vector128)) return Add(data, Unsafe.As>(ref value)); - if (typeof(T) == typeof(string)) - return value == null ? Add(data, 0) : AddString(data, ((string)(object)value).AsSpan()); - if (typeof(T) == typeof(Memory)) - return AddString(data, ((Memory)(object)value!).Span); - if (typeof(T) == typeof(ReadOnlyMemory)) - return AddString(data, ((Memory)(object)value!).Span); - if (typeof(T) == typeof(Memory)) - return AddBytes(data, ((Memory)(object)value!).Span); - if (typeof(T) == typeof(ReadOnlyMemory)) - return AddBytes(data, ((Memory)(object)value!).Span); - - return Add(data, Vector128.CreateScalar(value == null ? 0 : value.GetHashCode()).AsByte()); + return Add(data, Vector128.CreateScalar(value?.GetHashCode() ?? 0).AsByte()); } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] @@ -130,112 +119,110 @@ public static AHasher2Data AddBytes(AHasher2Data data, ReadOnlySpan value) if (value.Length <= 8) { - data = Add(data, ReadSmall(value)); + 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) { - Span> tail = stackalloc Vector128[4] -#if NET8_0_OR_GREATER - { - Vector128.LoadUnsafe(in value[^(sizeof(ulong) * 2)]), - Vector128.LoadUnsafe(in value[^(sizeof(ulong) * 4)]), - Vector128.LoadUnsafe(in value[^(sizeof(ulong) * 6)]), - Vector128.LoadUnsafe(in value[^(sizeof(ulong) * 8)]), - }; -#else - { - Vector128.LoadUnsafe(ref Unsafe.AsRef(in value[^(sizeof(ulong) * 2)])), - Vector128.LoadUnsafe(ref Unsafe.AsRef(in value[^(sizeof(ulong) * 4)])), - Vector128.LoadUnsafe(ref Unsafe.AsRef(in value[^(sizeof(ulong) * 6)])), - Vector128.LoadUnsafe(ref Unsafe.AsRef(in value[^(sizeof(ulong) * 8)])), - }; -#endif - Span> current = stackalloc Vector128[4] - { data.key, data.key, data.key, data.key }; - current[0] = AesEnc(current[0], tail[0]); - current[1] = AesEnc(current[1], tail[1]); - current[2] = AesEnc(current[2], tail[2]); - current[3] = AesEnc(current[3], tail[3]); - Span> sum = stackalloc Vector128[2] { data.key, ~data.key }; - sum[0] = AddByU64(sum[0], tail[0]); - sum[1] = AddByU64(sum[1], tail[1]); - sum[0] = ShuffleAndAdd(sum[0], tail[2]); - sum[1] = ShuffleAndAdd(sum[1], tail[3]); - - // tail will not use again - for (; value.Length > 64; value = value[(sizeof(ulong) * 2 * 4)..]) - { - var blocks = tail; -#if NET8_0_OR_GREATER - blocks[0] = Vector128.LoadUnsafe(in value[0]); - blocks[1] = Vector128.LoadUnsafe(in value[sizeof(ulong) * 2]); - blocks[2] = Vector128.LoadUnsafe(in value[sizeof(ulong) * 4]); - blocks[3] = Vector128.LoadUnsafe(in value[sizeof(ulong) * 6]); -#else - blocks[0] = Vector128.LoadUnsafe(ref Unsafe.AsRef(in value[0])); - blocks[1] = Vector128.LoadUnsafe(ref Unsafe.AsRef(in value[sizeof(ulong) * 2])); - blocks[2] = Vector128.LoadUnsafe(ref Unsafe.AsRef(in value[sizeof(ulong) * 4])); - blocks[3] = Vector128.LoadUnsafe(ref Unsafe.AsRef(in value[sizeof(ulong) * 6])); -#endif + return AddBytes_len_64(data, value); - current[0] = AesEnc(current[0], blocks[0]); - current[1] = AesEnc(current[1], blocks[1]); - current[2] = AesEnc(current[2], blocks[2]); - current[3] = AesEnc(current[3], blocks[3]); - sum[0] = ShuffleAndAdd(sum[0], blocks[0]); - sum[1] = ShuffleAndAdd(sum[1], blocks[1]); - sum[0] = ShuffleAndAdd(sum[0], blocks[2]); - sum[1] = ShuffleAndAdd(sum[1], blocks[3]); + [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; } - - data = Add(data, current[0]); - data = Add(data, current[1]); - data = Add(data, current[2]); - data = Add(data, current[3]); - data = Add(data, sum[0]); - data = Add(data, sum[1]); } else // 33 .. 64 { -#if NET8_0_OR_GREATER - 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)]); -#else - var a = Vector128.LoadUnsafe(ref Unsafe.AsRef(in value[0])); - var b = Vector128.LoadUnsafe(ref Unsafe.AsRef(in value[sizeof(ulong) * 2])); - var c = Vector128.LoadUnsafe(ref Unsafe.AsRef(in value[^(sizeof(ulong) * 4)])); - var d = Vector128.LoadUnsafe(ref Unsafe.AsRef(in value[^(sizeof(ulong) * 2)])); -#endif - data = Add(data, a); - data = Add(data, b); - data = Add(data, c); - data = Add(data, d); + 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 { -#if NET8_0_OR_GREATER - var a = Vector128.LoadUnsafe(in value[0]); - var b = Vector128.LoadUnsafe(in value[^(sizeof(ulong) * 2)]); -#else - var a = Vector128.LoadUnsafe(ref Unsafe.AsRef(in value[0])); - var b = Vector128.LoadUnsafe(ref Unsafe.AsRef(in value[^(sizeof(ulong) * 2)])); -#endif - data = Add(data, a); - data = Add(data, b); + 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 { - var a = Vector128.Create(MemoryMarshal.Cast(value)[0], - MemoryMarshal.Cast(value.Slice(value.Length - sizeof(ulong), sizeof(ulong)))[0]); - data = Add(data, a); - } + return AddBytes_len_9_16(data, value); - return data; + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + static AHasher2Data AddBytes_len_9_16(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; + } + } } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] @@ -243,20 +230,34 @@ public static AHasher2Data AddString(AHasher2Data data, ReadOnlySpan value { if (value.Length > 8) { - data = AddBytes(data, value); - data.enc = AesEnc(data.sum, data.enc); - data.enc = AesDec(AesDec(data.enc, data.key), data.enc); + 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 { - data = AddLength(data, value.Length); + 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); + 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; + } } - return data; } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] @@ -286,19 +287,25 @@ private static AHasher2Data AddLength(AHasher2Data data, int len) 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.Create((ulong)value[0], value[0]).AsByte(), - 2 or 3 => Vector128.Create((ulong)MemoryMarshal.Cast(value)[0], value[^1]).AsByte(), - _ => Vector128.Create((ulong)MemoryMarshal.Cast(value)[0], - MemoryMarshal.Cast(value.Slice(value.Length - sizeof(uint), sizeof(uint)))[0]) - .AsByte() + 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)), }; } From b17a8d892b04b57d63b8b536d3bbbb9a6c4630a4 Mon Sep 17 00:00:00 2001 From: U2A5F Date: Fri, 29 Dec 2023 16:31:27 +0800 Subject: [PATCH 5/7] soft --- Benchmark/BenchmarkAHashString.cs | 13 ++ .../Cryptography/AHasher/AHasher2.cs | 34 +--- .../Cryptography/AHasher/AHasher2Data.cs | 31 ++- .../Cryptography/AHasher/AesHasher.cs | 10 +- .../Cryptography/AHasher/SoftHasher.cs | 191 +++++++++++++++++- 5 files changed, 241 insertions(+), 38 deletions(-) diff --git a/Benchmark/BenchmarkAHashString.cs b/Benchmark/BenchmarkAHashString.cs index 927d2d1..e4fa4b4 100644 --- a/Benchmark/BenchmarkAHashString.cs +++ b/Benchmark/BenchmarkAHashString.cs @@ -33,6 +33,19 @@ public int AHash_AddString_2() 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(AHasher2.GlobalRandomState); + hasher = SoftHasher.AddString(hasher, Str); + r += SoftHasher.ToHashCode(hasher); + } + return r; + } #endif [Benchmark(Baseline = true)] diff --git a/BetterCollections/Cryptography/AHasher/AHasher2.cs b/BetterCollections/Cryptography/AHasher/AHasher2.cs index d33928a..2216b28 100644 --- a/BetterCollections/Cryptography/AHasher/AHasher2.cs +++ b/BetterCollections/Cryptography/AHasher/AHasher2.cs @@ -91,14 +91,7 @@ public static AHasher2 ThreadCurrent public AHasher2(AHasherRandomState randomState) { Unsafe.SkipInit(out this); - if (AesHasher.IsSupported) - { - data = AesHasher.Create(randomState); - } - else - { - throw new NotImplementedException(); - } + data = AesHasher.IsSupported ? AesHasher.Create(randomState) : SoftHasher.Create(randomState); } #region IHasher @@ -106,59 +99,52 @@ public AHasher2(AHasherRandomState randomState) [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public void Add(int value) { - if (AesHasher.IsSupported) data = AesHasher.Add(data, value); - else throw new NotImplementedException(); + data = AesHasher.IsSupported ? AesHasher.Add(data, value) : SoftHasher.Add(data, value); } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public void Add(long value) { - if (AesHasher.IsSupported) data = AesHasher.Add(data, value); - else throw new NotImplementedException(); + data = AesHasher.IsSupported ? AesHasher.Add(data, value) : SoftHasher.Add(data, value); } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public void Add(T value) { - if (AesHasher.IsSupported) data = AesHasher.Add(data, value); - else throw new NotImplementedException(); + data = AesHasher.IsSupported ? AesHasher.Add(data, value) : SoftHasher.Add(data, value); } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public void Add(T value, IEqualityComparer? comparer) { - if (AesHasher.IsSupported) data = AesHasher.Add(data, value, comparer); - else throw new NotImplementedException(); + data = AesHasher.IsSupported ? AesHasher.Add(data, value, comparer) : SoftHasher.Add(data, value, comparer); } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public void AddBytes(ReadOnlySpan value) { - if (AesHasher.IsSupported) data = AesHasher.AddBytes(data, value); - else throw new NotImplementedException(); + data = AesHasher.IsSupported ? AesHasher.AddBytes(data, value) : SoftHasher.AddBytes(data, value); } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public void AddString(ReadOnlySpan value) { - if (AesHasher.IsSupported) data = AesHasher.AddString(data, value); - else throw new NotImplementedException(); + data = AesHasher.IsSupported ? AesHasher.AddString(data, value) : SoftHasher.AddString(data, value); } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public void AddString(ReadOnlySpan value) { - if (AesHasher.IsSupported) data = AesHasher.AddString(data, value); - else throw new NotImplementedException(); + data = AesHasher.IsSupported ? AesHasher.AddString(data, value) : SoftHasher.AddString(data, value); } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public int ToHashCode() => - AesHasher.IsSupported ? AesHasher.ToHashCode(data) : throw new NotImplementedException(); + AesHasher.IsSupported ? AesHasher.ToHashCode(data) : SoftHasher.ToHashCode(data); [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public long ToHashCodeLong() => - AesHasher.IsSupported ? AesHasher.ToHashCodeLong(data) : throw new NotImplementedException(); + AesHasher.IsSupported ? AesHasher.ToHashCodeLong(data) : SoftHasher.ToHashCodeLong(data); #endregion diff --git a/BetterCollections/Cryptography/AHasher/AHasher2Data.cs b/BetterCollections/Cryptography/AHasher/AHasher2Data.cs index c6be17e..b17b744 100644 --- a/BetterCollections/Cryptography/AHasher/AHasher2Data.cs +++ b/BetterCollections/Cryptography/AHasher/AHasher2Data.cs @@ -27,26 +27,43 @@ public AHasher2Data(Vector128 enc) this.enc = enc; } - [UnscopedRef] - public ref ulong buffer + public ulong buffer { [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - get => ref Unsafe.As(ref this); + 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(); } - [UnscopedRef] - public ref ulong pad + public Vector128 buffer_extra1 { [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - get => ref Unsafe.Add(ref Unsafe.As(ref this), 1); + get => Vector128.ConditionalSelect(Vector128.Create(ulong.MaxValue, 0), enc.AsUInt64(), sum.AsUInt64()); } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public AHasher2Data(ulong buffer, ulong pad) + public AHasher2Data(ulong buffer, ulong pad, Vector128 extra) { Unsafe.SkipInit(out this); this.buffer = buffer; this.pad = pad; + this.extra = extra.AsUInt64(); } } diff --git a/BetterCollections/Cryptography/AHasher/AesHasher.cs b/BetterCollections/Cryptography/AHasher/AesHasher.cs index 8750fec..bf61130 100644 --- a/BetterCollections/Cryptography/AHasher/AesHasher.cs +++ b/BetterCollections/Cryptography/AHasher/AesHasher.cs @@ -216,11 +216,11 @@ static AHasher2Data AddBytes_len_17_32(AHasher2Data data, ReadOnlySpan val [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] static AHasher2Data AddBytes_len_9_16(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; + 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); } } } diff --git a/BetterCollections/Cryptography/AHasher/SoftHasher.cs b/BetterCollections/Cryptography/AHasher/SoftHasher.cs index 832af86..2ea610d 100644 --- a/BetterCollections/Cryptography/AHasher/SoftHasher.cs +++ b/BetterCollections/Cryptography/AHasher/SoftHasher.cs @@ -1,12 +1,199 @@ #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 struct SoftHasher { } +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)), + }; + } +} #endif From 1c029ff75eacfe1fb71dd659538478025b20cdd2 Mon Sep 17 00:00:00 2001 From: U2A5F Date: Fri, 29 Dec 2023 16:34:47 +0800 Subject: [PATCH 6/7] Combine --- .../Cryptography/AHasher/AHasher2.cs | 62 ++++++++++-- .../Cryptography/AHasher/SoftHasher.cs | 98 +++++++++++++++++++ 2 files changed, 150 insertions(+), 10 deletions(-) diff --git a/BetterCollections/Cryptography/AHasher/AHasher2.cs b/BetterCollections/Cryptography/AHasher/AHasher2.cs index 2216b28..302a3c5 100644 --- a/BetterCollections/Cryptography/AHasher/AHasher2.cs +++ b/BetterCollections/Cryptography/AHasher/AHasher2.cs @@ -155,21 +155,63 @@ public long ToHashCodeLong() => [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public static int Combine(T1 value1) { - if (AesHasher.IsSupported) - { - return AesHasher.Combine(value1); - } - throw new NotImplementedException(); + return AesHasher.IsSupported ? AesHasher.Combine(value1) : SoftHasher.Combine(value1); } [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public static int Combine(T1 value1, T2 value2) { - if (AesHasher.IsSupported) - { - return AesHasher.Combine(value1, value2); - } - throw new NotImplementedException(); + 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 diff --git a/BetterCollections/Cryptography/AHasher/SoftHasher.cs b/BetterCollections/Cryptography/AHasher/SoftHasher.cs index 2ea610d..92eba69 100644 --- a/BetterCollections/Cryptography/AHasher/SoftHasher.cs +++ b/BetterCollections/Cryptography/AHasher/SoftHasher.cs @@ -194,6 +194,104 @@ private static Vector128 ReadSmall(ReadOnlySpan value) _ => throw new ArgumentOutOfRangeException(nameof(value)), }; } + + #region Combine + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public static int Combine(T1 value1) + { + var data = Create(AHasher2._globalRandomState); + data = Add(data, value1); + return ToHashCode(data); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] + public static int Combine(T1 value1, T2 value2) + { + var data = Create(AHasher2._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(AHasher2._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(AHasher2._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(AHasher2._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(AHasher2._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(AHasher2._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(AHasher2._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 From cfeb7d08d9b2501896b7aeff47565f6da5595940 Mon Sep 17 00:00:00 2001 From: U2A5F Date: Fri, 29 Dec 2023 16:40:41 +0800 Subject: [PATCH 7/7] rename and test --- Benchmark/BenchmarkAHash.cs | 8 +- Benchmark/BenchmarkAHashString.cs | 6 +- .../AHasher/{AHasher2.cs => AHasher.cs} | 14 ++-- .../Cryptography/AHasher/AesHasher.cs | 16 ++-- .../Cryptography/AHasher/SoftHasher.cs | 16 ++-- .../{AHasher.cs => AHasherLegacy.cs} | 14 ++-- BetterCollections/Cryptography/IHasher.cs | 13 +++- BetterCollections/Cryptography/IHasher2.cs | 15 ---- .../Cryptography/IHasherLegacy.cs | 6 ++ BetterCollections/Misc/Utils.cs | 10 +++ Tests/TestAHasher.cs | 21 ++--- Tests/TestAHasherLegacy.cs | 76 +++++++++++++++++++ 12 files changed, 150 insertions(+), 65 deletions(-) rename BetterCollections/Cryptography/AHasher/{AHasher2.cs => AHasher.cs} (95%) rename BetterCollections/Cryptography/{AHasher.cs => AHasherLegacy.cs} (95%) delete mode 100644 BetterCollections/Cryptography/IHasher2.cs create mode 100644 BetterCollections/Cryptography/IHasherLegacy.cs create mode 100644 Tests/TestAHasherLegacy.cs diff --git a/Benchmark/BenchmarkAHash.cs b/Benchmark/BenchmarkAHash.cs index 960b337..6702acd 100644 --- a/Benchmark/BenchmarkAHash.cs +++ b/Benchmark/BenchmarkAHash.cs @@ -12,7 +12,7 @@ namespace Benchmark; [DisassemblyDiagnoser] public class BenchmarkAHash { - private readonly AHasher AHasher = new(); + private readonly AHasherLegacy aHasherLegacy = new(); [Benchmark] public ulong AHash() @@ -20,7 +20,7 @@ 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; } @@ -32,7 +32,7 @@ public int AHash2() var r = 0; for (int i = 0; i < 1000; i++) { - r += AHasher2.Combine(i); + r += AHasher.Combine(i); } return r; } @@ -43,7 +43,7 @@ public int AHash3() var r = 0; for (int i = 0; i < 1000; i++) { - var hasher = AesHasher.Create(AHasher2.GlobalRandomState); + var hasher = AesHasher.Create(AHasher.GlobalRandomState); hasher = AesHasher.Add(hasher, i); r += AesHasher.ToHashCode(hasher); } diff --git a/Benchmark/BenchmarkAHashString.cs b/Benchmark/BenchmarkAHashString.cs index e4fa4b4..bed4c35 100644 --- a/Benchmark/BenchmarkAHashString.cs +++ b/Benchmark/BenchmarkAHashString.cs @@ -21,7 +21,7 @@ public class BenchmarkAHashString [Benchmark] public int AHash_AddString_1() { - var hasher = AHasher2.Global; + var hasher = AHasher.Global; hasher.AddString(Str); return hasher.ToHashCode(); } @@ -29,7 +29,7 @@ public int AHash_AddString_1() [Benchmark] public int AHash_AddString_2() { - var hasher = AesHasher.Create(AHasher2.GlobalRandomState); + var hasher = AesHasher.Create(AHasher.GlobalRandomState); hasher = AesHasher.AddString(hasher, Str); return AesHasher.ToHashCode(hasher); } @@ -40,7 +40,7 @@ public int Soft_AddString1() var r = 0; for (int i = 0; i < 1000; i++) { - var hasher = SoftHasher.Create(AHasher2.GlobalRandomState); + var hasher = SoftHasher.Create(AHasher.GlobalRandomState); hasher = SoftHasher.AddString(hasher, Str); r += SoftHasher.ToHashCode(hasher); } diff --git a/BetterCollections/Cryptography/AHasher/AHasher2.cs b/BetterCollections/Cryptography/AHasher/AHasher.cs similarity index 95% rename from BetterCollections/Cryptography/AHasher/AHasher2.cs rename to BetterCollections/Cryptography/AHasher/AHasher.cs index 302a3c5..2b1e04e 100644 --- a/BetterCollections/Cryptography/AHasher/AHasher2.cs +++ b/BetterCollections/Cryptography/AHasher/AHasher.cs @@ -4,10 +4,6 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using BetterCollections.Cryptography.AHasherImpl; -using System.Runtime.Intrinsics; -using X86 = System.Runtime.Intrinsics.X86; -using Arm = System.Runtime.Intrinsics.Arm; - namespace BetterCollections.Cryptography; @@ -17,7 +13,7 @@ namespace BetterCollections.Cryptography; /// A hasher that ensures even distribution of each bit /// If possible use Aes SIMD acceleration (.net7+) /// -public struct AHasher2 : IHasher2 +public struct AHasher : IHasher { #region GlobalRandomState @@ -66,20 +62,20 @@ public static AHasherRandomState GenerateRandomState() #region Create - public static AHasher2 Global + public static AHasher Global { [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] get; } = new(GlobalRandomState); - public static AHasher2 ThreadCurrent + public static AHasher ThreadCurrent { [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] get; } = new(ThreadCurrentRandomState); [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public static AHasher2 CreateRandom() => new(GenerateRandomState()); + public static AHasher CreateRandom() => new(GenerateRandomState()); #endregion @@ -88,7 +84,7 @@ public static AHasher2 ThreadCurrent private AHasher2Data data; [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] - public AHasher2(AHasherRandomState randomState) + public AHasher(AHasherRandomState randomState) { Unsafe.SkipInit(out this); data = AesHasher.IsSupported ? AesHasher.Create(randomState) : SoftHasher.Create(randomState); diff --git a/BetterCollections/Cryptography/AHasher/AesHasher.cs b/BetterCollections/Cryptography/AHasher/AesHasher.cs index bf61130..4b4a489 100644 --- a/BetterCollections/Cryptography/AHasher/AesHasher.cs +++ b/BetterCollections/Cryptography/AHasher/AesHasher.cs @@ -361,7 +361,7 @@ private static Vector128 AddByU64(Vector128 a, Vector128 b) => [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public static int Combine(T1 value1) { - var data = Create(AHasher2._globalRandomState); + var data = Create(AHasher._globalRandomState); data = Add(data, value1); return ToHashCode(data); } @@ -369,7 +369,7 @@ public static int Combine(T1 value1) [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public static int Combine(T1 value1, T2 value2) { - var data = Create(AHasher2._globalRandomState); + var data = Create(AHasher._globalRandomState); data = Add(data, value1); data = Add(data, value2); return ToHashCode(data); @@ -378,7 +378,7 @@ public static int Combine(T1 value1, T2 value2) [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public static int Combine(T1 value1, T2 value2, T3 value3) { - var data = Create(AHasher2._globalRandomState); + var data = Create(AHasher._globalRandomState); data = Add(data, value1); data = Add(data, value2); data = Add(data, value3); @@ -388,7 +388,7 @@ public static int Combine(T1 value1, T2 value2, T3 value3) [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4) { - var data = Create(AHasher2._globalRandomState); + var data = Create(AHasher._globalRandomState); data = Add(data, value1); data = Add(data, value2); data = Add(data, value3); @@ -399,7 +399,7 @@ public static int Combine(T1 value1, T2 value2, T3 value3, T4 va [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5) { - var data = Create(AHasher2._globalRandomState); + var data = Create(AHasher._globalRandomState); data = Add(data, value1); data = Add(data, value2); data = Add(data, value3); @@ -411,7 +411,7 @@ public static int Combine(T1 value1, T2 value2, T3 value3, T [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6) { - var data = Create(AHasher2._globalRandomState); + var data = Create(AHasher._globalRandomState); data = Add(data, value1); data = Add(data, value2); data = Add(data, value3); @@ -425,7 +425,7 @@ public static int Combine(T1 value1, T2 value2, T3 value public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7) { - var data = Create(AHasher2._globalRandomState); + var data = Create(AHasher._globalRandomState); data = Add(data, value1); data = Add(data, value2); data = Add(data, value3); @@ -440,7 +440,7 @@ public static int Combine(T1 value1, T2 value2, T3 v public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7, T8 value8) { - var data = Create(AHasher2._globalRandomState); + var data = Create(AHasher._globalRandomState); data = Add(data, value1); data = Add(data, value2); data = Add(data, value3); diff --git a/BetterCollections/Cryptography/AHasher/SoftHasher.cs b/BetterCollections/Cryptography/AHasher/SoftHasher.cs index 92eba69..4fef73a 100644 --- a/BetterCollections/Cryptography/AHasher/SoftHasher.cs +++ b/BetterCollections/Cryptography/AHasher/SoftHasher.cs @@ -200,7 +200,7 @@ private static Vector128 ReadSmall(ReadOnlySpan value) [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public static int Combine(T1 value1) { - var data = Create(AHasher2._globalRandomState); + var data = Create(AHasher._globalRandomState); data = Add(data, value1); return ToHashCode(data); } @@ -208,7 +208,7 @@ public static int Combine(T1 value1) [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public static int Combine(T1 value1, T2 value2) { - var data = Create(AHasher2._globalRandomState); + var data = Create(AHasher._globalRandomState); data = Add(data, value1); data = Add(data, value2); return ToHashCode(data); @@ -217,7 +217,7 @@ public static int Combine(T1 value1, T2 value2) [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public static int Combine(T1 value1, T2 value2, T3 value3) { - var data = Create(AHasher2._globalRandomState); + var data = Create(AHasher._globalRandomState); data = Add(data, value1); data = Add(data, value2); data = Add(data, value3); @@ -227,7 +227,7 @@ public static int Combine(T1 value1, T2 value2, T3 value3) [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4) { - var data = Create(AHasher2._globalRandomState); + var data = Create(AHasher._globalRandomState); data = Add(data, value1); data = Add(data, value2); data = Add(data, value3); @@ -238,7 +238,7 @@ public static int Combine(T1 value1, T2 value2, T3 value3, T4 va [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5) { - var data = Create(AHasher2._globalRandomState); + var data = Create(AHasher._globalRandomState); data = Add(data, value1); data = Add(data, value2); data = Add(data, value3); @@ -250,7 +250,7 @@ public static int Combine(T1 value1, T2 value2, T3 value3, T [MethodImpl(MethodImplOptions.AggressiveInlining), SkipLocalsInit] public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6) { - var data = Create(AHasher2._globalRandomState); + var data = Create(AHasher._globalRandomState); data = Add(data, value1); data = Add(data, value2); data = Add(data, value3); @@ -264,7 +264,7 @@ public static int Combine(T1 value1, T2 value2, T3 value public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7) { - var data = Create(AHasher2._globalRandomState); + var data = Create(AHasher._globalRandomState); data = Add(data, value1); data = Add(data, value2); data = Add(data, value3); @@ -279,7 +279,7 @@ public static int Combine(T1 value1, T2 value2, T3 v public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7, T8 value8) { - var data = Create(AHasher2._globalRandomState); + var data = Create(AHasher._globalRandomState); data = Add(data, value1); data = Add(data, value2); data = Add(data, value3); 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 80ae5c4..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]; @@ -61,11 +61,11 @@ public AHasher() } [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 @@ -79,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) @@ -149,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(); @@ -161,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/IHasher2.cs b/BetterCollections/Cryptography/IHasher2.cs deleted file mode 100644 index b532092..0000000 --- a/BetterCollections/Cryptography/IHasher2.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace BetterCollections.Cryptography; - -public interface IHasher2 -{ - 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/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)); + } +}