From 559d006d31105c207a877665e7590b5004bb7382 Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Thu, 2 Nov 2017 01:53:21 -0700 Subject: [PATCH 01/11] HashCode based on xxHash32 (#14354) Works by maintaining the xxHash32 state variables (v1 -> v4, length) as well as a queue of values that fall outside of the block size (16 bytes/4 ints). Seed is assumed to be zero as there is no seed ctor in the proposal - however, it is possible to add a seed without changing the structure. Tests against known xxHash32 vectors are provided ("abcd1234efgh5678..."). --- src/System.Runtime/ref/System.Runtime.cs | 53 ++++ src/System.Runtime/src/System.Runtime.csproj | 4 +- src/System.Runtime/src/System/HashCode.cs | 250 ++++++++++++++++++ .../tests/Performance/Perf.HashCode.cs | 28 ++ .../System.Runtime.Performance.Tests.csproj | 1 + .../tests/System.Runtime.Tests.csproj | 1 + .../tests/System/HashCodeTests.cs | 91 +++++++ 7 files changed, 427 insertions(+), 1 deletion(-) create mode 100644 src/System.Runtime/src/System/HashCode.cs create mode 100644 src/System.Runtime/tests/Performance/Perf.HashCode.cs create mode 100644 src/System.Runtime/tests/System/HashCodeTests.cs diff --git a/src/System.Runtime/ref/System.Runtime.cs b/src/System.Runtime/ref/System.Runtime.cs index 82ffee5373de..6cdaf2c44bbf 100644 --- a/src/System.Runtime/ref/System.Runtime.cs +++ b/src/System.Runtime/ref/System.Runtime.cs @@ -3675,6 +3675,59 @@ public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, S public void SetTarget(T target) { } public bool TryGetTarget(out T target) { throw null; } } + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Explicit, Size = 8 * 8)] + public partial struct HashCode + { + [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + [System.Runtime.TargetedPatchingOptOutAttribute("Performance critical for inlining across NGen images.")] + public int ToHashCode() { throw null; } + +#pragma warning disable 0809 + [System.ObsoleteAttribute("Use ToHashCode to retrieve the computed hash code.", error: true)] + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + public override int GetHashCode() { throw null; } +#pragma warning restore 0809 + + [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + [System.Runtime.TargetedPatchingOptOutAttribute("Performance critical for inlining across NGen images.")] + public void Add(T value) { } + + [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + [System.Runtime.TargetedPatchingOptOutAttribute("Performance critical for inlining across NGen images.")] + public void Add(T value, System.Collections.Generic.IEqualityComparer comparer) { } + + [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + [System.Runtime.TargetedPatchingOptOutAttribute("Performance critical for inlining across NGen images.")] + public static int Combine(T1 value1) { throw null; } + + [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + [System.Runtime.TargetedPatchingOptOutAttribute("Performance critical for inlining across NGen images.")] + public static int Combine(T1 value1, T2 value2) { throw null; } + + [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + [System.Runtime.TargetedPatchingOptOutAttribute("Performance critical for inlining across NGen images.")] + public static int Combine(T1 value1, T2 value2, T3 value3) { throw null; } + + [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + [System.Runtime.TargetedPatchingOptOutAttribute("Performance critical for inlining across NGen images.")] + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4) { throw null; } + + [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + [System.Runtime.TargetedPatchingOptOutAttribute("Performance critical for inlining across NGen images.")] + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5) { throw null; } + + [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + [System.Runtime.TargetedPatchingOptOutAttribute("Performance critical for inlining across NGen images.")] + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6) { throw null; } + + [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + [System.Runtime.TargetedPatchingOptOutAttribute("Performance critical for inlining across NGen images.")] + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7) { throw null; } + + [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + [System.Runtime.TargetedPatchingOptOutAttribute("Performance critical for inlining across NGen images.")] + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7, T8 value8) { throw null; } + } } namespace System.Runtime.ConstrainedExecution diff --git a/src/System.Runtime/src/System.Runtime.csproj b/src/System.Runtime/src/System.Runtime.csproj index 0129b51f901f..5569251577c0 100644 --- a/src/System.Runtime/src/System.Runtime.csproj +++ b/src/System.Runtime/src/System.Runtime.csproj @@ -5,6 +5,7 @@ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2} System.Runtime true + true @@ -18,6 +19,7 @@ + @@ -33,4 +35,4 @@ - \ No newline at end of file + diff --git a/src/System.Runtime/src/System/HashCode.cs b/src/System.Runtime/src/System/HashCode.cs new file mode 100644 index 000000000000..5c926da9c384 --- /dev/null +++ b/src/System.Runtime/src/System/HashCode.cs @@ -0,0 +1,250 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime; +using System.Runtime.CompilerServices; + +namespace System +{ + public unsafe struct HashCode + { + private const uint Queue = 4; + private const uint Length = 7; + + private const uint Prime1 = 2654435761U; + private const uint Prime2 = 2246822519U; + private const uint Prime3 = 3266489917U; + private const uint Prime4 = 0668265263U; + private const uint Prime5 = 0374761393U; + + // 8x8 = 64 bytes = 1 x86 cache line + // +----+----+----+----+----+----+----+----+ + // | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | + // | xxHash State | Queue | Le | + // +----+----+----+----+----+----+----+----+ + private fixed uint _state[8]; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [TargetedPatchingOptOut("Performance critical for inlining across NGen images.")] + private void Add(int value) + { + unchecked + { + var val = (uint)value; + + fixed (uint* state = _state) + { + var queue = state + Queue; + var position = state[Length] & 0x3; + + if (position < 3) + { + queue[position] = val; + } + else + { + + var sentinel = + state[0] | state[1] | state[2] | state[3] | // any of accumulators set? + (state[Length] ^ position); // length greater than 3? + + if (sentinel == 0) + { + // Initialize + + state[0] = Prime1 + Prime2; + state[1] = Prime2; + state[2] = 0; + state[3] = (uint)-Prime1; + } + + for (var i = 0; i < 3; i++) + state[i] = Round(state[i], queue[i]); + state[3] = Round(state[3], val); + } + + state[Length]++; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [TargetedPatchingOptOut("Performance critical for inlining across NGen images.")] + public int ToHashCode() + { + unchecked + { + fixed (uint* state = _state) + { + var queue = state + Queue; + var position = state[Length] & 0x3; + + // Mix the accumulators (if they contain anything) + + var hash = state[Length] > 3 + ? Rol(state[0], 1) + Rol(state[1], 7) + Rol(state[2], 12) + Rol(state[3], 18) + : Prime5; + + hash += state[Length] * 4; + + // Mix what remains in the queue + + for (var i = 0; i < position; i++) + { + hash += queue[i] * Prime3; + hash = Rol(hash, 17) * Prime4; + } + + // Final mix + + hash ^= hash >> 15; + hash *= Prime2; + hash ^= hash >> 13; + hash *= Prime3; + hash ^= hash >> 16; + + return (int)hash; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [TargetedPatchingOptOut("Performance critical for inlining across NGen images.")] + private uint Round(uint seed, uint input) + { + unchecked + { + seed += input * Prime2; + seed = Rol(seed, 13); + seed *= Prime1; + return seed; + } + } + +# pragma warning disable 0809 + [Obsolete("Use ToHashCode to retrieve the computed hash code.", error: true)] + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() => ToHashCode(); +# pragma warning restore 0809 + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [TargetedPatchingOptOut("Performance critical for inlining across NGen images.")] + private static uint Rol(uint value, int count) + => (value << count) | (value >> (32 - count)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [TargetedPatchingOptOut("Performance critical for inlining across NGen images.")] + public void Add(T value) => Add(value?.GetHashCode() ?? 0); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [TargetedPatchingOptOut("Performance critical for inlining across NGen images.")] + public void Add(T value, IEqualityComparer comparer) + { + if (comparer is null) throw new ArgumentNullException(nameof(comparer)); + Add(comparer.GetHashCode(value)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [TargetedPatchingOptOut("Performance critical for inlining across NGen images.")] + public static int Combine(T1 value1) + { + var hc = new HashCode(); + hc.Add(value1); + return hc.ToHashCode(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [TargetedPatchingOptOut("Performance critical for inlining across NGen images.")] + public static int Combine(T1 value1, T2 value2) + { + var hc = new HashCode(); + hc.Add(value1); + hc.Add(value2); + return hc.ToHashCode(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [TargetedPatchingOptOut("Performance critical for inlining across NGen images.")] + public static int Combine(T1 value1, T2 value2, T3 value3) + { + var hc = new HashCode(); + hc.Add(value1); + hc.Add(value2); + hc.Add(value3); + return hc.ToHashCode(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [TargetedPatchingOptOut("Performance critical for inlining across NGen images.")] + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4) + { + var hc = new HashCode(); + hc.Add(value1); + hc.Add(value2); + hc.Add(value3); + hc.Add(value4); + return hc.ToHashCode(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [TargetedPatchingOptOut("Performance critical for inlining across NGen images.")] + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5) + { + var hc = new HashCode(); + hc.Add(value1); + hc.Add(value2); + hc.Add(value3); + hc.Add(value4); + hc.Add(value5); + return hc.ToHashCode(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [TargetedPatchingOptOut("Performance critical for inlining across NGen images.")] + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6) + { + var hc = new HashCode(); + hc.Add(value1); + hc.Add(value2); + hc.Add(value3); + hc.Add(value4); + hc.Add(value5); + hc.Add(value6); + return hc.ToHashCode(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [TargetedPatchingOptOut("Performance critical for inlining across NGen images.")] + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7) + { + var hc = new HashCode(); + hc.Add(value1); + hc.Add(value2); + hc.Add(value3); + hc.Add(value4); + hc.Add(value5); + hc.Add(value6); + hc.Add(value7); + return hc.ToHashCode(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [TargetedPatchingOptOut("Performance critical for inlining across NGen images.")] + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7, T8 value8) + { + var hc = new HashCode(); + hc.Add(value1); + hc.Add(value2); + hc.Add(value3); + hc.Add(value4); + hc.Add(value5); + hc.Add(value6); + hc.Add(value7); + hc.Add(value8); + return hc.ToHashCode(); + } + } +} diff --git a/src/System.Runtime/tests/Performance/Perf.HashCode.cs b/src/System.Runtime/tests/Performance/Perf.HashCode.cs new file mode 100644 index 000000000000..8bcea2b1990f --- /dev/null +++ b/src/System.Runtime/tests/Performance/Perf.HashCode.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Xunit.Performance; + +namespace System.Tests +{ + public class Perf_HashCode + { + [Benchmark] + public void Add_() + { + foreach (var iteration in Benchmark.Iterations) + using (iteration.StartMeasurement()) + { + var hc = new HashCode(); + for (int i = 0; i < 10000; i++) + { + hc.Add(i); hc.Add(i); hc.Add(i); + hc.Add(i); hc.Add(i); hc.Add(i); + hc.Add(i); hc.Add(i); hc.Add(i); + } + hc.ToHashCode(); + } + } + } +} diff --git a/src/System.Runtime/tests/Performance/System.Runtime.Performance.Tests.csproj b/src/System.Runtime/tests/Performance/System.Runtime.Performance.Tests.csproj index e7be566250ac..d65f1a0c6eb1 100644 --- a/src/System.Runtime/tests/Performance/System.Runtime.Performance.Tests.csproj +++ b/src/System.Runtime/tests/Performance/System.Runtime.Performance.Tests.csproj @@ -15,6 +15,7 @@ + diff --git a/src/System.Runtime/tests/System.Runtime.Tests.csproj b/src/System.Runtime/tests/System.Runtime.Tests.csproj index 527199e58d0c..2cc950d38f3a 100644 --- a/src/System.Runtime/tests/System.Runtime.Tests.csproj +++ b/src/System.Runtime/tests/System.Runtime.Tests.csproj @@ -47,6 +47,7 @@ Common\System\Reflection\MockParameterInfo.cs + diff --git a/src/System.Runtime/tests/System/HashCodeTests.cs b/src/System.Runtime/tests/System/HashCodeTests.cs new file mode 100644 index 000000000000..1c5a229f4b7b --- /dev/null +++ b/src/System.Runtime/tests/System/HashCodeTests.cs @@ -0,0 +1,91 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Xunit; + +public static class HashCodeTests +{ + [Theory] + [InlineData(0x02cc5d05U)] + [InlineData(0xa3643705U, 0x64636261U)] + [InlineData(0x4603e94cU, 0x64636261U, 0x33323130U)] + [InlineData(0xd8a1e80fU, 0x64636261U, 0x33323130U, 0x68676665U)] + [InlineData(0x4b62a7cfU, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U)] + [InlineData(0xc33a7641U, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U, 0x6c6b6a69U)] + [InlineData(0x1a794705U, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U, 0x6c6b6a69U, 0x31303938U)] + [InlineData(0x4d79177dU, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U, 0x6c6b6a69U, 0x31303938U, 0x706f6e6dU)] + [InlineData(0x59d79205U, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U, 0x6c6b6a69U, 0x31303938U, 0x706f6e6dU, 0x35343332U)] + [InlineData(0x49585aaeU, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U, 0x6c6b6a69U, 0x31303938U, 0x706f6e6dU, 0x35343332U, 0x74737271U)] + [InlineData(0x2f005ff1U, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U, 0x6c6b6a69U, 0x31303938U, 0x706f6e6dU, 0x35343332U, 0x74737271U, 0x39383736U)] + [InlineData(0x0ce339bdU, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U, 0x6c6b6a69U, 0x31303938U, 0x706f6e6dU, 0x35343332U, 0x74737271U, 0x39383736U, 0x78777675U)] + [InlineData(0xb31bd2ffU, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U, 0x6c6b6a69U, 0x31303938U, 0x706f6e6dU, 0x35343332U, 0x74737271U, 0x39383736U, 0x78777675U, 0x33323130U)] + [InlineData(0xa821efa3U, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U, 0x6c6b6a69U, 0x31303938U, 0x706f6e6dU, 0x35343332U, 0x74737271U, 0x39383736U, 0x78777675U, 0x33323130U, 0x62617a79U)] + public static void HashCode_Add(uint expected, params uint[] vector) + { + var hc = new HashCode(); + for (var i = 0; i < vector.Length; i++) + hc.Add(vector[i]); + + Assert.Equal(expected, unchecked((uint)hc.ToHashCode())); + } + + [Fact] + public static void HashCode_Add_Generic() + { + var hc = new HashCode(); + hc.Add(1); + hc.Add(100.2); + + Assert.Equal(-273013950, hc.ToHashCode()); + } + + [Fact] + public static void HashCode_Add_GenericEqualityComparer() + { + var hc = new HashCode(); + hc.Add(1); + hc.Add("Hello", new ConstComparer()); + + Assert.NotEqual(-1844029331, hc.ToHashCode()); + } + + [Fact] + public static void HashCode_Combine() + { + var hcs = new[] + { + HashCode.Combine(1), + HashCode.Combine(1, 2), + HashCode.Combine(1, 2, 3), + HashCode.Combine(1, 2, 3, 4), + HashCode.Combine(1, 2, 3, 4, 5), + HashCode.Combine(1, 2, 3, 4, 5, 6), + HashCode.Combine(1, 2, 3, 4, 5, 6, 7), + HashCode.Combine(1, 2, 3, 4, 5, 6, 7, 8), + + HashCode.Combine(2), + HashCode.Combine(2, 3), + HashCode.Combine(2, 3, 4), + HashCode.Combine(2, 3, 4, 5), + HashCode.Combine(2, 3, 4, 5, 6), + HashCode.Combine(2, 3, 4, 5, 6, 7), + HashCode.Combine(2, 3, 4, 5, 6, 7, 8), + HashCode.Combine(2, 3, 4, 5, 6, 7, 8, 9), + }; + + for (var i = 0; i < hcs.Length; i++) + for (var j = 0; j < hcs.Length; j++) + { + if (i == j) continue; + Assert.NotEqual(hcs[i], hcs[j]); + } + } + + public class ConstComparer : System.Collections.Generic.IEqualityComparer + { + public bool Equals(string x, string y) => false; + public int GetHashCode(string obj) => 1; + } +} From e9a5e0a4e815dd0f5dd83f829de625e6ce0325e9 Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Thu, 2 Nov 2017 18:05:45 -0700 Subject: [PATCH 02/11] Feedback on PR Order of members now matches the proposal, if that matters. Combine methods are unrolled for perf. Removed superfluous attributes. Test vector tests are now optional (behind SYSTEM_HASHCODE_TESTVECTORS def) as the random seed will break these tests. Defining SYSTEM_HASHCODE_TESTVECTORS will disable the per-AppDomain random seed. Combine is now tested against the behavior of Add. --- src/System.Runtime/ref/System.Runtime.cs | 49 +- src/System.Runtime/src/System/HashCode.cs | 464 ++++++++++++------ .../tests/System/HashCodeTests.cs | 105 +++- 3 files changed, 436 insertions(+), 182 deletions(-) diff --git a/src/System.Runtime/ref/System.Runtime.cs b/src/System.Runtime/ref/System.Runtime.cs index 6cdaf2c44bbf..a92bdfc9cf07 100644 --- a/src/System.Runtime/ref/System.Runtime.cs +++ b/src/System.Runtime/ref/System.Runtime.cs @@ -3675,58 +3675,55 @@ public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, S public void SetTarget(T target) { } public bool TryGetTarget(out T target) { throw null; } } - [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Explicit, Size = 8 * 8)] + + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Auto)] public partial struct HashCode { - [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - [System.Runtime.TargetedPatchingOptOutAttribute("Performance critical for inlining across NGen images.")] - public int ToHashCode() { throw null; } - -#pragma warning disable 0809 - [System.ObsoleteAttribute("Use ToHashCode to retrieve the computed hash code.", error: true)] - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public override int GetHashCode() { throw null; } -#pragma warning restore 0809 - - [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - [System.Runtime.TargetedPatchingOptOutAttribute("Performance critical for inlining across NGen images.")] - public void Add(T value) { } + private uint _v1, _v2, _v3, _v4; + private uint _queue1, _queue2, _queue3; + private uint _length; [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - [System.Runtime.TargetedPatchingOptOutAttribute("Performance critical for inlining across NGen images.")] - public void Add(T value, System.Collections.Generic.IEqualityComparer comparer) { } - - [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - [System.Runtime.TargetedPatchingOptOutAttribute("Performance critical for inlining across NGen images.")] public static int Combine(T1 value1) { throw null; } [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - [System.Runtime.TargetedPatchingOptOutAttribute("Performance critical for inlining across NGen images.")] public static int Combine(T1 value1, T2 value2) { throw null; } [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - [System.Runtime.TargetedPatchingOptOutAttribute("Performance critical for inlining across NGen images.")] public static int Combine(T1 value1, T2 value2, T3 value3) { throw null; } [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - [System.Runtime.TargetedPatchingOptOutAttribute("Performance critical for inlining across NGen images.")] public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4) { throw null; } [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - [System.Runtime.TargetedPatchingOptOutAttribute("Performance critical for inlining across NGen images.")] public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5) { throw null; } [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - [System.Runtime.TargetedPatchingOptOutAttribute("Performance critical for inlining across NGen images.")] public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6) { throw null; } [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - [System.Runtime.TargetedPatchingOptOutAttribute("Performance critical for inlining across NGen images.")] public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7) { throw null; } [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - [System.Runtime.TargetedPatchingOptOutAttribute("Performance critical for inlining across NGen images.")] public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7, T8 value8) { throw null; } + + [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public void Add(T value) { } + + [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public void Add(T value, System.Collections.Generic.IEqualityComparer comparer) { } + + [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public int ToHashCode() { throw null; } + +# pragma warning disable 0809 + + [System.ObsoleteAttribute("Use ToHashCode to retrieve the computed hash code.", error: true)] + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + public override int GetHashCode() { throw null; } + +# pragma warning restore 0809 + } } diff --git a/src/System.Runtime/src/System/HashCode.cs b/src/System.Runtime/src/System/HashCode.cs index 5c926da9c384..c075730c71eb 100644 --- a/src/System.Runtime/src/System/HashCode.cs +++ b/src/System.Runtime/src/System/HashCode.cs @@ -4,247 +4,403 @@ using System.Collections.Generic; using System.ComponentModel; -using System.Runtime; using System.Runtime.CompilerServices; namespace System { - public unsafe struct HashCode + public struct HashCode { - private const uint Queue = 4; - private const uint Length = 7; +# if SYSTEM_HASHCODE_TESTVECTORS + private static readonly uint _seed = 0; +# else + private static readonly uint _seed = unchecked((uint)new Random().Next(int.MinValue, int.MaxValue)); +# endif private const uint Prime1 = 2654435761U; private const uint Prime2 = 2246822519U; private const uint Prime3 = 3266489917U; private const uint Prime4 = 0668265263U; private const uint Prime5 = 0374761393U; - - // 8x8 = 64 bytes = 1 x86 cache line - // +----+----+----+----+----+----+----+----+ - // | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | - // | xxHash State | Queue | Le | - // +----+----+----+----+----+----+----+----+ - private fixed uint _state[8]; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [TargetedPatchingOptOut("Performance critical for inlining across NGen images.")] - private void Add(int value) + + private uint _v1, _v2, _v3, _v4; + private uint _queue1, _queue2, _queue3; + private uint _length; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Combine(T1 value1) { unchecked { - var val = (uint)value; + var hc1 = (uint)(value1?.GetHashCode() ?? 0); - fixed (uint* state = _state) - { - var queue = state + Queue; - var position = state[Length] & 0x3; + var hash = MixEmptyState(); + hash += 4; - if (position < 3) - { - queue[position] = val; - } - else - { + hash = QueueRound(hash, hc1); - var sentinel = - state[0] | state[1] | state[2] | state[3] | // any of accumulators set? - (state[Length] ^ position); // length greater than 3? + hash = MixFinal(hash); + return (int)hash; + } + } - if (sentinel == 0) - { - // Initialize + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Combine(T1 value1, T2 value2) + { + unchecked + { + var hc1 = (uint)(value1?.GetHashCode() ?? 0); + var hc2 = (uint)(value2?.GetHashCode() ?? 0); - state[0] = Prime1 + Prime2; - state[1] = Prime2; - state[2] = 0; - state[3] = (uint)-Prime1; - } + var hash = MixEmptyState(); + hash += 8; - for (var i = 0; i < 3; i++) - state[i] = Round(state[i], queue[i]); - state[3] = Round(state[3], val); - } + hash = QueueRound(hash, hc1); + hash = QueueRound(hash, hc2); - state[Length]++; - } + hash = MixFinal(hash); + return (int)hash; } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - [TargetedPatchingOptOut("Performance critical for inlining across NGen images.")] - public int ToHashCode() + public static int Combine(T1 value1, T2 value2, T3 value3) { unchecked { - fixed (uint* state = _state) - { - var queue = state + Queue; - var position = state[Length] & 0x3; + var hc1 = (uint)(value1?.GetHashCode() ?? 0); + var hc2 = (uint)(value2?.GetHashCode() ?? 0); + var hc3 = (uint)(value3?.GetHashCode() ?? 0); - // Mix the accumulators (if they contain anything) + var hash = MixEmptyState(); + hash += 12; - var hash = state[Length] > 3 - ? Rol(state[0], 1) + Rol(state[1], 7) + Rol(state[2], 12) + Rol(state[3], 18) - : Prime5; + hash = QueueRound(hash, hc1); + hash = QueueRound(hash, hc2); + hash = QueueRound(hash, hc3); - hash += state[Length] * 4; + hash = MixFinal(hash); + return (int)hash; + } + } - // Mix what remains in the queue + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4) + { + unchecked + { + var hc1 = (uint)(value1?.GetHashCode() ?? 0); + var hc2 = (uint)(value2?.GetHashCode() ?? 0); + var hc3 = (uint)(value3?.GetHashCode() ?? 0); + var hc4 = (uint)(value4?.GetHashCode() ?? 0); - for (var i = 0; i < position; i++) - { - hash += queue[i] * Prime3; - hash = Rol(hash, 17) * Prime4; - } + Initialize(out var v1, out var v2, out var v3, out var v4); - // Final mix + v1 = Round(v1, hc1); + v2 = Round(v2, hc2); + v3 = Round(v3, hc3); + v4 = Round(v4, hc4); - hash ^= hash >> 15; - hash *= Prime2; - hash ^= hash >> 13; - hash *= Prime3; - hash ^= hash >> 16; + var hash = MixState(v1, v2, v3, v4); + hash += 16; - return (int)hash; - } + hash = MixFinal(hash); + return (int)hash; } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - [TargetedPatchingOptOut("Performance critical for inlining across NGen images.")] - private uint Round(uint seed, uint input) + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5) { unchecked { - seed += input * Prime2; - seed = Rol(seed, 13); - seed *= Prime1; - return seed; + var hc1 = (uint)(value1?.GetHashCode() ?? 0); + var hc2 = (uint)(value2?.GetHashCode() ?? 0); + var hc3 = (uint)(value3?.GetHashCode() ?? 0); + var hc4 = (uint)(value4?.GetHashCode() ?? 0); + var hc5 = (uint)(value5?.GetHashCode() ?? 0); + + Initialize(out var v1, out var v2, out var v3, out var v4); + + v1 = Round(v1, hc1); + v2 = Round(v2, hc2); + v3 = Round(v3, hc3); + v4 = Round(v4, hc4); + + var hash = MixState(v1, v2, v3, v4); + hash += 20; + + hash = QueueRound(hash, hc5); + + hash = MixFinal(hash); + return (int)hash; } } -# pragma warning disable 0809 - [Obsolete("Use ToHashCode to retrieve the computed hash code.", error: true)] - [EditorBrowsable(EditorBrowsableState.Never)] - public override int GetHashCode() => ToHashCode(); -# pragma warning restore 0809 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6) + { + unchecked + { + var hc1 = (uint)(value1?.GetHashCode() ?? 0); + var hc2 = (uint)(value2?.GetHashCode() ?? 0); + var hc3 = (uint)(value3?.GetHashCode() ?? 0); + var hc4 = (uint)(value4?.GetHashCode() ?? 0); + var hc5 = (uint)(value5?.GetHashCode() ?? 0); + var hc6 = (uint)(value6?.GetHashCode() ?? 0); + + Initialize(out var v1, out var v2, out var v3, out var v4); + + v1 = Round(v1, hc1); + v2 = Round(v2, hc2); + v3 = Round(v3, hc3); + v4 = Round(v4, hc4); + + var hash = MixState(v1, v2, v3, v4); + hash += 24; + + hash = QueueRound(hash, hc5); + hash = QueueRound(hash, hc6); + + hash = MixFinal(hash); + return (int)hash; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7) + { + unchecked + { + var hc1 = (uint)(value1?.GetHashCode() ?? 0); + var hc2 = (uint)(value2?.GetHashCode() ?? 0); + var hc3 = (uint)(value3?.GetHashCode() ?? 0); + var hc4 = (uint)(value4?.GetHashCode() ?? 0); + var hc5 = (uint)(value5?.GetHashCode() ?? 0); + var hc6 = (uint)(value6?.GetHashCode() ?? 0); + var hc7 = (uint)(value7?.GetHashCode() ?? 0); + + Initialize(out var v1, out var v2, out var v3, out var v4); + + v1 = Round(v1, hc1); + v2 = Round(v2, hc2); + v3 = Round(v3, hc3); + v4 = Round(v4, hc4); + + var hash = MixState(v1, v2, v3, v4); + hash += 28; + + hash = QueueRound(hash, hc5); + hash = QueueRound(hash, hc6); + hash = QueueRound(hash, hc7); + + hash = MixFinal(hash); + return (int)hash; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7, T8 value8) + { + unchecked + { + var hc1 = (uint)(value1?.GetHashCode() ?? 0); + var hc2 = (uint)(value2?.GetHashCode() ?? 0); + var hc3 = (uint)(value3?.GetHashCode() ?? 0); + var hc4 = (uint)(value4?.GetHashCode() ?? 0); + var hc5 = (uint)(value5?.GetHashCode() ?? 0); + var hc6 = (uint)(value6?.GetHashCode() ?? 0); + var hc7 = (uint)(value7?.GetHashCode() ?? 0); + var hc8 = (uint)(value8?.GetHashCode() ?? 0); + + Initialize(out var v1, out var v2, out var v3, out var v4); + + v1 = Round(v1, hc1); + v2 = Round(v2, hc2); + v3 = Round(v3, hc3); + v4 = Round(v4, hc4); + + v1 = Round(v1, hc5); + v2 = Round(v2, hc6); + v3 = Round(v3, hc7); + v4 = Round(v4, hc8); + + var hash = MixState(v1, v2, v3, v4); + hash += 32; + + hash = MixFinal(hash); + return (int)hash; + } + } [MethodImpl(MethodImplOptions.AggressiveInlining)] - [TargetedPatchingOptOut("Performance critical for inlining across NGen images.")] private static uint Rol(uint value, int count) => (value << count) | (value >> (32 - count)); [MethodImpl(MethodImplOptions.AggressiveInlining)] - [TargetedPatchingOptOut("Performance critical for inlining across NGen images.")] - public void Add(T value) => Add(value?.GetHashCode() ?? 0); + private static void Initialize(out uint v1, out uint v2, out uint v3, out uint v4) + { + unchecked + { + v1 = _seed + Prime1 + Prime2; + v2 = _seed + Prime2; + v3 = _seed + 0; + v4 = _seed - Prime1; + } + } [MethodImpl(MethodImplOptions.AggressiveInlining)] - [TargetedPatchingOptOut("Performance critical for inlining across NGen images.")] - public void Add(T value, IEqualityComparer comparer) + private static uint Round(uint hash, uint input) { - if (comparer is null) throw new ArgumentNullException(nameof(comparer)); - Add(comparer.GetHashCode(value)); + unchecked + { + hash += input * Prime2; + hash = Rol(hash, 13); + hash *= Prime1; + return hash; + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - [TargetedPatchingOptOut("Performance critical for inlining across NGen images.")] - public static int Combine(T1 value1) + private static uint QueueRound(uint hash, uint queuedValue) { - var hc = new HashCode(); - hc.Add(value1); - return hc.ToHashCode(); + unchecked + { + hash += queuedValue * Prime3; + return Rol(hash, 17) * Prime4; + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - [TargetedPatchingOptOut("Performance critical for inlining across NGen images.")] - public static int Combine(T1 value1, T2 value2) + private static uint MixState(uint v1, uint v2, uint v3, uint v4) { - var hc = new HashCode(); - hc.Add(value1); - hc.Add(value2); - return hc.ToHashCode(); + return Rol(v1, 1) + Rol(v2, 7) + Rol(v3, 12) + Rol(v4, 18); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - [TargetedPatchingOptOut("Performance critical for inlining across NGen images.")] - public static int Combine(T1 value1, T2 value2, T3 value3) + private static uint MixEmptyState() { - var hc = new HashCode(); - hc.Add(value1); - hc.Add(value2); - hc.Add(value3); - return hc.ToHashCode(); + return _seed + Prime5; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - [TargetedPatchingOptOut("Performance critical for inlining across NGen images.")] - public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4) + private static uint MixFinal(uint hash) { - var hc = new HashCode(); - hc.Add(value1); - hc.Add(value2); - hc.Add(value3); - hc.Add(value4); - return hc.ToHashCode(); + unchecked + { + hash ^= hash >> 15; + hash *= Prime2; + hash ^= hash >> 13; + hash *= Prime3; + hash ^= hash >> 16; + return hash; + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - [TargetedPatchingOptOut("Performance critical for inlining across NGen images.")] - public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5) + public void Add(T value) { - var hc = new HashCode(); - hc.Add(value1); - hc.Add(value2); - hc.Add(value3); - hc.Add(value4); - hc.Add(value5); - return hc.ToHashCode(); + Add(value?.GetHashCode() ?? 0); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - [TargetedPatchingOptOut("Performance critical for inlining across NGen images.")] - public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6) + public void Add(T value, IEqualityComparer comparer) { - var hc = new HashCode(); - hc.Add(value1); - hc.Add(value2); - hc.Add(value3); - hc.Add(value4); - hc.Add(value5); - hc.Add(value6); - return hc.ToHashCode(); + if (comparer is null) + throw new ArgumentNullException(nameof(comparer)); + Add(comparer.GetHashCode(value)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - [TargetedPatchingOptOut("Performance critical for inlining across NGen images.")] - public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7) + private void Add(int value) { - var hc = new HashCode(); - hc.Add(value1); - hc.Add(value2); - hc.Add(value3); - hc.Add(value4); - hc.Add(value5); - hc.Add(value6); - hc.Add(value7); - return hc.ToHashCode(); + unchecked + { + var val = (uint)value; + var position = _length & 0x3; + + switch (position) + { + case 0: + _queue1 = val; + break; + case 1: + _queue2 = val; + break; + case 2: + _queue3 = val; + break; + default: + + var sentinel = + _v1 | _v2 | _v3 | _v4 | // any of accumulators set? + (_length ^ position); // length greater than 3? + + if (sentinel == 0) + Initialize(out _v1, out _v2, out _v3, out _v4); + + _v1 = Round(_v1, _queue1); + _v2 = Round(_v2, _queue2); + _v3 = Round(_v3, _queue3); + _v4 = Round(_v4, val); + + break; + } + + _length++; + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - [TargetedPatchingOptOut("Performance critical for inlining across NGen images.")] - public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7, T8 value8) + public int ToHashCode() { - var hc = new HashCode(); - hc.Add(value1); - hc.Add(value2); - hc.Add(value3); - hc.Add(value4); - hc.Add(value5); - hc.Add(value6); - hc.Add(value7); - hc.Add(value8); - return hc.ToHashCode(); + unchecked + { + var position = _length & 0x3; + + // If the length is less than 3, _v1 -> _v4 don't contain + // anything yet. xxHash32 treats this differently. + + var hash = (_length ^ position) == 0 + ? MixEmptyState() + : MixState(_v1, _v2, _v3, _v4); + + hash += _length * 4; + + // Mix what remains in the queue + + switch (position) + { + case 1: + hash = QueueRound(hash, _queue1); + break; + + case 2: + hash = QueueRound(hash, _queue1); + hash = QueueRound(hash, _queue2); + break; + + case 3: + hash = QueueRound(hash, _queue1); + hash = QueueRound(hash, _queue2); + hash = QueueRound(hash, _queue3); + break; + } + + // Final mix + + hash = MixFinal(hash); + + return (int)hash; + } } + +#pragma warning disable 0809 + + [Obsolete("Use ToHashCode to retrieve the computed hash code.", error: true)] + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() => throw new NotImplementedException(); + +#pragma warning restore 0809 + } } diff --git a/src/System.Runtime/tests/System/HashCodeTests.cs b/src/System.Runtime/tests/System/HashCodeTests.cs index 1c5a229f4b7b..de8acc608c1d 100644 --- a/src/System.Runtime/tests/System/HashCodeTests.cs +++ b/src/System.Runtime/tests/System/HashCodeTests.cs @@ -7,6 +7,8 @@ public static class HashCodeTests { + +# if SYSTEM_HASHCODE_TESTVECTORS [Theory] [InlineData(0x02cc5d05U)] [InlineData(0xa3643705U, 0x64636261U)] @@ -40,6 +42,7 @@ public static void HashCode_Add_Generic() Assert.Equal(-273013950, hc.ToHashCode()); } +# endif [Fact] public static void HashCode_Add_GenericEqualityComparer() @@ -48,7 +51,11 @@ public static void HashCode_Add_GenericEqualityComparer() hc.Add(1); hc.Add("Hello", new ConstComparer()); - Assert.NotEqual(-1844029331, hc.ToHashCode()); + var expected = new HashCode(); + hc.Add(1); + hc.Add(ConstComparer.ConstantValue); + + Assert.NotEqual(expected.ToHashCode(), hc.ToHashCode()); } [Fact] @@ -83,9 +90,103 @@ public static void HashCode_Combine() } } + [Fact] + public static void HashCode_Combine_Add_1() + { + var hc = new HashCode(); + hc.Add(1); + Assert.Equal(hc.ToHashCode(), HashCode.Combine(1)); + } + + [Fact] + public static void HashCode_Combine_Add_2() + { + var hc = new HashCode(); + hc.Add(1); + hc.Add(2); + Assert.Equal(hc.ToHashCode(), HashCode.Combine(1, 2)); + } + + [Fact] + public static void HashCode_Combine_Add_3() + { + var hc = new HashCode(); + hc.Add(1); + hc.Add(2); + hc.Add(3); + Assert.Equal(hc.ToHashCode(), HashCode.Combine(1, 2, 3)); + } + + [Fact] + public static void HashCode_Combine_Add_4() + { + var hc = new HashCode(); + hc.Add(1); + hc.Add(2); + hc.Add(3); + hc.Add(4); + Assert.Equal(hc.ToHashCode(), HashCode.Combine(1, 2, 3, 4)); + } + + [Fact] + public static void HashCode_Combine_Add_5() + { + var hc = new HashCode(); + hc.Add(1); + hc.Add(2); + hc.Add(3); + hc.Add(4); + hc.Add(5); + Assert.Equal(hc.ToHashCode(), HashCode.Combine(1, 2, 3, 4, 5)); + } + + [Fact] + public static void HashCode_Combine_Add_6() + { + var hc = new HashCode(); + hc.Add(1); + hc.Add(2); + hc.Add(3); + hc.Add(4); + hc.Add(5); + hc.Add(6); + Assert.Equal(hc.ToHashCode(), HashCode.Combine(1, 2, 3, 4, 5, 6)); + } + + [Fact] + public static void HashCode_Combine_Add_7() + { + var hc = new HashCode(); + hc.Add(1); + hc.Add(2); + hc.Add(3); + hc.Add(4); + hc.Add(5); + hc.Add(6); + hc.Add(7); + Assert.Equal(hc.ToHashCode(), HashCode.Combine(1, 2, 3, 4, 5, 6, 7)); + } + + [Fact] + public static void HashCode_Combine_Add_8() + { + var hc = new HashCode(); + hc.Add(1); + hc.Add(2); + hc.Add(3); + hc.Add(4); + hc.Add(5); + hc.Add(6); + hc.Add(7); + hc.Add(8); + Assert.Equal(hc.ToHashCode(), HashCode.Combine(1, 2, 3, 4, 5, 6, 7, 8)); + } + public class ConstComparer : System.Collections.Generic.IEqualityComparer { + public const int ConstantValue = 1234; + public bool Equals(string x, string y) => false; - public int GetHashCode(string obj) => 1; + public int GetHashCode(string obj) => ConstantValue; } } From 9690fa0ff187835111fabe7e08d1a3490fd36d15 Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Fri, 3 Nov 2017 10:52:03 -0700 Subject: [PATCH 03/11] Feature system hashcode staged (#1) PR Feeback * Code style cleanups. * Unessecary fields and structlayout removed from ref. * Correct attribution applied. * More perf tests. * Perf tests prevent the jitter from removing dead code that we might care about. * Fixed false positive in tests. * Don't use float types for tests. * Remove AllowUnsafeBlocks. * Disable compilation for netfx. * Removing 0 prefix from constants. * Removing unchecked scopes. * Removing 4 reads of s_seed in Initialize. * Ensure that all methods are inlined. Some parameters changed to ref to do this. * Changing exception to NotSupportedException. * GetHashCode is NoInlining so that it is display in the stack trace. --- THIRD-PARTY-NOTICES.TXT | 30 +- src/System.Runtime/ref/System.Runtime.cs | 5 - src/System.Runtime/src/System.Runtime.csproj | 1 - src/System.Runtime/src/System/HashCode.cs | 516 +++++++++--------- .../tests/Performance/Perf.HashCode.cs | 195 ++++++- .../tests/System.Runtime.Tests.csproj | 6 +- .../tests/System/HashCodeTests.cs | 31 +- 7 files changed, 510 insertions(+), 274 deletions(-) diff --git a/THIRD-PARTY-NOTICES.TXT b/THIRD-PARTY-NOTICES.TXT index 06055ff03ecd..152fb31b62b9 100644 --- a/THIRD-PARTY-NOTICES.TXT +++ b/THIRD-PARTY-NOTICES.TXT @@ -1,4 +1,4 @@ -.NET Core uses third-party libraries or other resources that may be +.NET Core uses third-party libraries or other resources that may be distributed under licenses different than the .NET Core software. In the event that we accidentally failed to list a required notice, please @@ -224,3 +224,31 @@ descriptions are © 1997-2005 Sean Eron Anderson. The code and descriptions are distributed in the hope that they will be useful, but WITHOUT ANY WARRANTY and without even the implied warranty of merchantability or fitness for a particular purpose. + +License notice for xxHash +------------------------- + +xxHash Library +Copyright (c) 2012-2014, Yann Collet +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/System.Runtime/ref/System.Runtime.cs b/src/System.Runtime/ref/System.Runtime.cs index a92bdfc9cf07..ef556175bea3 100644 --- a/src/System.Runtime/ref/System.Runtime.cs +++ b/src/System.Runtime/ref/System.Runtime.cs @@ -3676,13 +3676,8 @@ public void SetTarget(T target) { } public bool TryGetTarget(out T target) { throw null; } } - [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Auto)] public partial struct HashCode { - private uint _v1, _v2, _v3, _v4; - private uint _queue1, _queue2, _queue3; - private uint _length; - [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public static int Combine(T1 value1) { throw null; } diff --git a/src/System.Runtime/src/System.Runtime.csproj b/src/System.Runtime/src/System.Runtime.csproj index 5569251577c0..eca130a190b1 100644 --- a/src/System.Runtime/src/System.Runtime.csproj +++ b/src/System.Runtime/src/System.Runtime.csproj @@ -5,7 +5,6 @@ {56B9D0A9-44D3-488E-8B42-C14A6E30CAB2} System.Runtime true - true diff --git a/src/System.Runtime/src/System/HashCode.cs b/src/System.Runtime/src/System/HashCode.cs index c075730c71eb..05eb1916a746 100644 --- a/src/System.Runtime/src/System/HashCode.cs +++ b/src/System.Runtime/src/System/HashCode.cs @@ -2,25 +2,67 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +/* + +The xxHash32 implementation is based on the code published by Yann Collet: +https://raw.githubusercontent.com/Cyan4973/xxHash/5c174cfa4e45a42f94082dc0d4539b39696afea1/xxhash.c + + xxHash - Fast Hash algorithm + Copyright (C) 2012-2016, Yann Collet + + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - xxHash homepage: http://www.xxhash.com + - xxHash source repository : https://github.com/Cyan4973/xxHash + +*/ + using System.Collections.Generic; using System.ComponentModel; using System.Runtime.CompilerServices; namespace System { + // xxHash32 is used for the hash code. + // https://github.com/Cyan4973/xxHash + public struct HashCode { # if SYSTEM_HASHCODE_TESTVECTORS - private static readonly uint _seed = 0; + private static readonly uint s_seed = 0; # else - private static readonly uint _seed = unchecked((uint)new Random().Next(int.MinValue, int.MaxValue)); + private static readonly uint s_seed = (uint)new Random().Next(int.MinValue, int.MaxValue); # endif private const uint Prime1 = 2654435761U; private const uint Prime2 = 2246822519U; private const uint Prime3 = 3266489917U; - private const uint Prime4 = 0668265263U; - private const uint Prime5 = 0374761393U; + private const uint Prime4 = 668265263U; + private const uint Prime5 = 374761393U; private uint _v1, _v2, _v3, _v4; private uint _queue1, _queue2, _queue3; @@ -29,207 +71,183 @@ public struct HashCode [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int Combine(T1 value1) { - unchecked - { - var hc1 = (uint)(value1?.GetHashCode() ?? 0); + var hc1 = (uint)(value1?.GetHashCode() ?? 0); - var hash = MixEmptyState(); - hash += 4; + uint hash = MixEmptyState(); + hash += 4; - hash = QueueRound(hash, hc1); + QueueRound(ref hash, hc1); - hash = MixFinal(hash); - return (int)hash; - } + MixFinal(ref hash); + return (int)hash; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int Combine(T1 value1, T2 value2) { - unchecked - { - var hc1 = (uint)(value1?.GetHashCode() ?? 0); - var hc2 = (uint)(value2?.GetHashCode() ?? 0); + var hc1 = (uint)(value1?.GetHashCode() ?? 0); + var hc2 = (uint)(value2?.GetHashCode() ?? 0); - var hash = MixEmptyState(); - hash += 8; + uint hash = MixEmptyState(); + hash += 8; - hash = QueueRound(hash, hc1); - hash = QueueRound(hash, hc2); + QueueRound(ref hash, hc1); + QueueRound(ref hash, hc2); - hash = MixFinal(hash); - return (int)hash; - } + MixFinal(ref hash); + return (int)hash; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int Combine(T1 value1, T2 value2, T3 value3) { - unchecked - { - var hc1 = (uint)(value1?.GetHashCode() ?? 0); - var hc2 = (uint)(value2?.GetHashCode() ?? 0); - var hc3 = (uint)(value3?.GetHashCode() ?? 0); + var hc1 = (uint)(value1?.GetHashCode() ?? 0); + var hc2 = (uint)(value2?.GetHashCode() ?? 0); + var hc3 = (uint)(value3?.GetHashCode() ?? 0); - var hash = MixEmptyState(); - hash += 12; + uint hash = MixEmptyState(); + hash += 12; - hash = QueueRound(hash, hc1); - hash = QueueRound(hash, hc2); - hash = QueueRound(hash, hc3); + QueueRound(ref hash, hc1); + QueueRound(ref hash, hc2); + QueueRound(ref hash, hc3); - hash = MixFinal(hash); - return (int)hash; - } + MixFinal(ref hash); + return (int)hash; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4) { - unchecked - { - var hc1 = (uint)(value1?.GetHashCode() ?? 0); - var hc2 = (uint)(value2?.GetHashCode() ?? 0); - var hc3 = (uint)(value3?.GetHashCode() ?? 0); - var hc4 = (uint)(value4?.GetHashCode() ?? 0); + var hc1 = (uint)(value1?.GetHashCode() ?? 0); + var hc2 = (uint)(value2?.GetHashCode() ?? 0); + var hc3 = (uint)(value3?.GetHashCode() ?? 0); + var hc4 = (uint)(value4?.GetHashCode() ?? 0); - Initialize(out var v1, out var v2, out var v3, out var v4); + Initialize(out uint v1, out uint v2, out uint v3, out uint v4); - v1 = Round(v1, hc1); - v2 = Round(v2, hc2); - v3 = Round(v3, hc3); - v4 = Round(v4, hc4); + Round(ref v1, hc1); + Round(ref v2, hc2); + Round(ref v3, hc3); + Round(ref v4, hc4); - var hash = MixState(v1, v2, v3, v4); - hash += 16; + uint hash = MixState(v1, v2, v3, v4); + hash += 16; - hash = MixFinal(hash); - return (int)hash; - } + MixFinal(ref hash); + return (int)hash; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5) { - unchecked - { - var hc1 = (uint)(value1?.GetHashCode() ?? 0); - var hc2 = (uint)(value2?.GetHashCode() ?? 0); - var hc3 = (uint)(value3?.GetHashCode() ?? 0); - var hc4 = (uint)(value4?.GetHashCode() ?? 0); - var hc5 = (uint)(value5?.GetHashCode() ?? 0); + var hc1 = (uint)(value1?.GetHashCode() ?? 0); + var hc2 = (uint)(value2?.GetHashCode() ?? 0); + var hc3 = (uint)(value3?.GetHashCode() ?? 0); + var hc4 = (uint)(value4?.GetHashCode() ?? 0); + var hc5 = (uint)(value5?.GetHashCode() ?? 0); - Initialize(out var v1, out var v2, out var v3, out var v4); + Initialize(out uint v1, out uint v2, out uint v3, out uint v4); - v1 = Round(v1, hc1); - v2 = Round(v2, hc2); - v3 = Round(v3, hc3); - v4 = Round(v4, hc4); + Round(ref v1, hc1); + Round(ref v2, hc2); + Round(ref v3, hc3); + Round(ref v4, hc4); - var hash = MixState(v1, v2, v3, v4); - hash += 20; + uint hash = MixState(v1, v2, v3, v4); + hash += 20; - hash = QueueRound(hash, hc5); + QueueRound(ref hash, hc5); - hash = MixFinal(hash); - return (int)hash; - } + MixFinal(ref hash); + return (int)hash; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6) { - unchecked - { - var hc1 = (uint)(value1?.GetHashCode() ?? 0); - var hc2 = (uint)(value2?.GetHashCode() ?? 0); - var hc3 = (uint)(value3?.GetHashCode() ?? 0); - var hc4 = (uint)(value4?.GetHashCode() ?? 0); - var hc5 = (uint)(value5?.GetHashCode() ?? 0); - var hc6 = (uint)(value6?.GetHashCode() ?? 0); + var hc1 = (uint)(value1?.GetHashCode() ?? 0); + var hc2 = (uint)(value2?.GetHashCode() ?? 0); + var hc3 = (uint)(value3?.GetHashCode() ?? 0); + var hc4 = (uint)(value4?.GetHashCode() ?? 0); + var hc5 = (uint)(value5?.GetHashCode() ?? 0); + var hc6 = (uint)(value6?.GetHashCode() ?? 0); - Initialize(out var v1, out var v2, out var v3, out var v4); + Initialize(out uint v1, out uint v2, out uint v3, out uint v4); - v1 = Round(v1, hc1); - v2 = Round(v2, hc2); - v3 = Round(v3, hc3); - v4 = Round(v4, hc4); + Round(ref v1, hc1); + Round(ref v2, hc2); + Round(ref v3, hc3); + Round(ref v4, hc4); - var hash = MixState(v1, v2, v3, v4); - hash += 24; + uint hash = MixState(v1, v2, v3, v4); + hash += 24; - hash = QueueRound(hash, hc5); - hash = QueueRound(hash, hc6); + QueueRound(ref hash, hc5); + QueueRound(ref hash, hc6); - hash = MixFinal(hash); - return (int)hash; - } + MixFinal(ref hash); + return (int)hash; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7) { - unchecked - { - var hc1 = (uint)(value1?.GetHashCode() ?? 0); - var hc2 = (uint)(value2?.GetHashCode() ?? 0); - var hc3 = (uint)(value3?.GetHashCode() ?? 0); - var hc4 = (uint)(value4?.GetHashCode() ?? 0); - var hc5 = (uint)(value5?.GetHashCode() ?? 0); - var hc6 = (uint)(value6?.GetHashCode() ?? 0); - var hc7 = (uint)(value7?.GetHashCode() ?? 0); - - Initialize(out var v1, out var v2, out var v3, out var v4); - - v1 = Round(v1, hc1); - v2 = Round(v2, hc2); - v3 = Round(v3, hc3); - v4 = Round(v4, hc4); - - var hash = MixState(v1, v2, v3, v4); - hash += 28; - - hash = QueueRound(hash, hc5); - hash = QueueRound(hash, hc6); - hash = QueueRound(hash, hc7); - - hash = MixFinal(hash); - return (int)hash; - } + var hc1 = (uint)(value1?.GetHashCode() ?? 0); + var hc2 = (uint)(value2?.GetHashCode() ?? 0); + var hc3 = (uint)(value3?.GetHashCode() ?? 0); + var hc4 = (uint)(value4?.GetHashCode() ?? 0); + var hc5 = (uint)(value5?.GetHashCode() ?? 0); + var hc6 = (uint)(value6?.GetHashCode() ?? 0); + var hc7 = (uint)(value7?.GetHashCode() ?? 0); + + Initialize(out uint v1, out uint v2, out uint v3, out uint v4); + + Round(ref v1, hc1); + Round(ref v2, hc2); + Round(ref v3, hc3); + Round(ref v4, hc4); + + uint hash = MixState(v1, v2, v3, v4); + hash += 28; + + QueueRound(ref hash, hc5); + QueueRound(ref hash, hc6); + QueueRound(ref hash, hc7); + + MixFinal(ref hash); + return (int)hash; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7, T8 value8) { - unchecked - { - var hc1 = (uint)(value1?.GetHashCode() ?? 0); - var hc2 = (uint)(value2?.GetHashCode() ?? 0); - var hc3 = (uint)(value3?.GetHashCode() ?? 0); - var hc4 = (uint)(value4?.GetHashCode() ?? 0); - var hc5 = (uint)(value5?.GetHashCode() ?? 0); - var hc6 = (uint)(value6?.GetHashCode() ?? 0); - var hc7 = (uint)(value7?.GetHashCode() ?? 0); - var hc8 = (uint)(value8?.GetHashCode() ?? 0); - - Initialize(out var v1, out var v2, out var v3, out var v4); - - v1 = Round(v1, hc1); - v2 = Round(v2, hc2); - v3 = Round(v3, hc3); - v4 = Round(v4, hc4); - - v1 = Round(v1, hc5); - v2 = Round(v2, hc6); - v3 = Round(v3, hc7); - v4 = Round(v4, hc8); - - var hash = MixState(v1, v2, v3, v4); - hash += 32; - - hash = MixFinal(hash); - return (int)hash; - } + var hc1 = (uint)(value1?.GetHashCode() ?? 0); + var hc2 = (uint)(value2?.GetHashCode() ?? 0); + var hc3 = (uint)(value3?.GetHashCode() ?? 0); + var hc4 = (uint)(value4?.GetHashCode() ?? 0); + var hc5 = (uint)(value5?.GetHashCode() ?? 0); + var hc6 = (uint)(value6?.GetHashCode() ?? 0); + var hc7 = (uint)(value7?.GetHashCode() ?? 0); + var hc8 = (uint)(value8?.GetHashCode() ?? 0); + + Initialize(out uint v1, out uint v2, out uint v3, out uint v4); + + Round(ref v1, hc1); + Round(ref v2, hc2); + Round(ref v3, hc3); + Round(ref v4, hc4); + + Round(ref v1, hc5); + Round(ref v2, hc6); + Round(ref v3, hc7); + Round(ref v4, hc8); + + uint hash = MixState(v1, v2, v3, v4); + hash += 32; + + MixFinal(ref hash); + return (int)hash; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -239,35 +257,26 @@ private static uint Rol(uint value, int count) [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void Initialize(out uint v1, out uint v2, out uint v3, out uint v4) { - unchecked - { - v1 = _seed + Prime1 + Prime2; - v2 = _seed + Prime2; - v3 = _seed + 0; - v4 = _seed - Prime1; - } + var seed = s_seed; + v1 = seed + Prime1 + Prime2; + v2 = seed + Prime2; + v3 = seed + 0; + v4 = seed - Prime1; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint Round(uint hash, uint input) + private static void Round(ref uint hash, uint input) { - unchecked - { - hash += input * Prime2; - hash = Rol(hash, 13); - hash *= Prime1; - return hash; - } + hash += input * Prime2; + hash = Rol(hash, 13); + hash *= Prime1; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint QueueRound(uint hash, uint queuedValue) + private static void QueueRound(ref uint hash, uint queuedValue) { - unchecked - { - hash += queuedValue * Prime3; - return Rol(hash, 17) * Prime4; - } + hash += queuedValue * Prime3; + hash = Rol(hash, 17) * Prime4; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -276,24 +285,19 @@ private static uint MixState(uint v1, uint v2, uint v3, uint v4) return Rol(v1, 1) + Rol(v2, 7) + Rol(v3, 12) + Rol(v4, 18); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint MixEmptyState() { - return _seed + Prime5; + return s_seed + Prime5; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint MixFinal(uint hash) + private static void MixFinal(ref uint hash) { - unchecked - { - hash ^= hash >> 15; - hash *= Prime2; - hash ^= hash >> 13; - hash *= Prime3; - hash ^= hash >> 16; - return hash; - } + hash ^= hash >> 15; + hash *= Prime2; + hash ^= hash >> 13; + hash *= Prime3; + hash ^= hash >> 16; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -313,94 +317,112 @@ public void Add(T value, IEqualityComparer comparer) [MethodImpl(MethodImplOptions.AggressiveInlining)] private void Add(int value) { - unchecked + // Note that x & 0x3 is like mod 3, but faster. + + var val = (uint)value; + uint position = _length & 0x3; + + // xxHash works as follows: + // 0. Initialize immediately. We can't do this in a struct (no default + // ctor). + // 1. Accumulate blocks of length 16 (4 uints) into 4 accumulators. + // 2. Accumulate remaining blocks of length 4 (1 uint) into the hash. + // 3. Accumulate remaining blocks of length 1 into the hash. + + // There is no need for *3 as this type only accepts ints. _queue1, + // _queue2 and _queue3 are basically a buffer so that when ToHashCode is + // called we can execute *2 correctly. That's what first three case + // statements do. + + // We need to initialize the xxHash32 state (_v1 -> _v4) lazily (see *0) + // and the last place that can be done if you look at the original code + // is just before the first block of 16 bytes is mixed in. The xxHash32 + // state is never used for streams containing fewer than 16 bytes. + + // A bloom filter is used to determine whether the default case statement + // has even been executed. To do that we check if the length is smaller + // than 4 (_length ^ position will be non-zero if this is the case). The + // case statement is for values larger than 2, so the check only succeeds + // on exactly 3. + + // To see what's really going on here, have a look at the Combine methods. + + if (position == 0) + _queue1 = val; + else if (position == 1) + _queue2 = val; + else if (position == 2) + _queue3 = val; + else { - var val = (uint)value; - var position = _length & 0x3; - - switch (position) - { - case 0: - _queue1 = val; - break; - case 1: - _queue2 = val; - break; - case 2: - _queue3 = val; - break; - default: - - var sentinel = - _v1 | _v2 | _v3 | _v4 | // any of accumulators set? - (_length ^ position); // length greater than 3? - - if (sentinel == 0) - Initialize(out _v1, out _v2, out _v3, out _v4); - - _v1 = Round(_v1, _queue1); - _v2 = Round(_v2, _queue2); - _v3 = Round(_v3, _queue3); - _v4 = Round(_v4, val); - - break; - } - - _length++; + // length smaller than 4? + if ((_length ^ position) == 0) + Initialize(out _v1, out _v2, out _v3, out _v4); + + Round(ref _v1, _queue1); + Round(ref _v2, _queue2); + Round(ref _v3, _queue3); + Round(ref _v4, val); } + + // Throw for more than uint.MaxValue fields. + _length = checked(_length + 1); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public int ToHashCode() { - unchecked - { - var position = _length & 0x3; + uint position = _length & 0x3; + + // If the length is less than 3, _v1 -> _v4 don't contain + // anything yet. xxHash32 treats this differently. - // If the length is less than 3, _v1 -> _v4 don't contain - // anything yet. xxHash32 treats this differently. + uint hash = (_length ^ position) == 0 + ? MixEmptyState() + : MixState(_v1, _v2, _v3, _v4); - var hash = (_length ^ position) == 0 - ? MixEmptyState() - : MixState(_v1, _v2, _v3, _v4); + // Multiply by 4 because we've been counting in ints, not + // bytes. - hash += _length * 4; + hash += _length * 4; - // Mix what remains in the queue + // Mix what remains in the queue - switch (position) + if (position > 0) + { + QueueRound(ref hash, _queue1); + if (position > 1) { - case 1: - hash = QueueRound(hash, _queue1); - break; - - case 2: - hash = QueueRound(hash, _queue1); - hash = QueueRound(hash, _queue2); - break; - - case 3: - hash = QueueRound(hash, _queue1); - hash = QueueRound(hash, _queue2); - hash = QueueRound(hash, _queue3); - break; + QueueRound(ref hash, _queue2); + if (position > 2) + QueueRound(ref hash, _queue3); } + } - // Final mix - - hash = MixFinal(hash); + MixFinal(ref hash); - return (int)hash; - } + return (int)hash; } -#pragma warning disable 0809 +# pragma warning disable 0809 + // Obsolete member 'memberA' overrides non-obsolete member 'memberB'. + // Disallowing GetHashCode is by design + + // * We decided to not override GetHashCode() to produce the hash code + // as this would be weird, both naming-wise as well as from a behavioral + // standpoint (GetHashCode() should return the object's hash code, not + // the one being computed). + + // * Even though ToHashCode() can be called safely multiple times on this + // implementation, it is not part of the contract. If the implementation + // has to change in the future we don't want to worry about people who + // might have incorrectly used this type. [Obsolete("Use ToHashCode to retrieve the computed hash code.", error: true)] [EditorBrowsable(EditorBrowsableState.Never)] - public override int GetHashCode() => throw new NotImplementedException(); - -#pragma warning restore 0809 + [MethodImpl(MethodImplOptions.NoInlining)] + public override int GetHashCode() => throw new NotSupportedException(); +# pragma warning restore 0809 } } diff --git a/src/System.Runtime/tests/Performance/Perf.HashCode.cs b/src/System.Runtime/tests/Performance/Perf.HashCode.cs index 8bcea2b1990f..d3def90ff7f3 100644 --- a/src/System.Runtime/tests/Performance/Perf.HashCode.cs +++ b/src/System.Runtime/tests/Performance/Perf.HashCode.cs @@ -2,27 +2,208 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Runtime.CompilerServices; using Microsoft.Xunit.Performance; namespace System.Tests { public class Perf_HashCode { + private static volatile int _valueStorage; + + // Prevents the jitter from eliminating code that + // we want to test. + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void DontDiscard(int value) + { + _valueStorage = value; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int DontFold(int value) + { + return value + _valueStorage; + } + + [Benchmark] + public void Add() + { + foreach (BenchmarkIteration iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + for (int i = 0; i < 100; i++) + { + var hc = new HashCode(); + for (int j = 0; j < 100; j++) + { + hc.Add(i); hc.Add(i); hc.Add(i); + hc.Add(i); hc.Add(i); hc.Add(i); + hc.Add(i); hc.Add(i); hc.Add(i); + } + DontDiscard(hc.ToHashCode()); + } + } + } + } + + [Benchmark] - public void Add_() + public void Combine_1() { - foreach (var iteration in Benchmark.Iterations) + foreach (BenchmarkIteration iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + for (int i = 0; i < 10000; i++) + { + DontDiscard(HashCode.Combine( + DontFold(i))); + } + } + } + } + + + [Benchmark] + public void Combine_2() + { + foreach (BenchmarkIteration iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + for (int i = 0; i < 10000; i++) + { + DontDiscard(HashCode.Combine( + DontFold(i), + DontFold(i))); + } + } + } + } + + [Benchmark] + public void Combine_3() + { + foreach (BenchmarkIteration iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + for (int i = 0; i < 10000; i++) + { + DontDiscard(HashCode.Combine( + DontFold(i), + DontFold(i), + DontFold(i))); + } + } + } + } + + [Benchmark] + public void Combine_4() + { + foreach (BenchmarkIteration iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + for (int i = 0; i < 10000; i++) + { + DontDiscard(HashCode.Combine( + DontFold(i), + DontFold(i), + DontFold(i), + DontFold(i))); + } + } + } + } + + [Benchmark] + public void Combine_5() + { + foreach (BenchmarkIteration iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + for (int i = 0; i < 10000; i++) + { + DontDiscard(HashCode.Combine( + DontFold(i), + DontFold(i), + DontFold(i), + DontFold(i), + DontFold(i))); + } + } + } + } + + [Benchmark] + public void Combine_6() + { + foreach (BenchmarkIteration iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + for (int i = 0; i < 10000; i++) + { + DontDiscard(HashCode.Combine( + DontFold(i), + DontFold(i), + DontFold(i), + DontFold(i), + DontFold(i), + DontFold(i))); + } + } + } + } + + [Benchmark] + public void Combine_7() + { + foreach (BenchmarkIteration iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + for (int i = 0; i < 10000; i++) + { + DontDiscard(HashCode.Combine( + DontFold(i), + DontFold(i), + DontFold(i), + DontFold(i), + DontFold(i), + DontFold(i), + DontFold(i))); + } + } + } + } + + [Benchmark] + public void Combine_8() + { + foreach (BenchmarkIteration iteration in Benchmark.Iterations) + { using (iteration.StartMeasurement()) { - var hc = new HashCode(); for (int i = 0; i < 10000; i++) { - hc.Add(i); hc.Add(i); hc.Add(i); - hc.Add(i); hc.Add(i); hc.Add(i); - hc.Add(i); hc.Add(i); hc.Add(i); + DontDiscard(HashCode.Combine( + DontFold(i), + DontFold(i), + DontFold(i), + DontFold(i), + DontFold(i), + DontFold(i), + DontFold(i), + DontFold(i))); } - hc.ToHashCode(); } + } } } } diff --git a/src/System.Runtime/tests/System.Runtime.Tests.csproj b/src/System.Runtime/tests/System.Runtime.Tests.csproj index 2cc950d38f3a..173377ea4451 100644 --- a/src/System.Runtime/tests/System.Runtime.Tests.csproj +++ b/src/System.Runtime/tests/System.Runtime.Tests.csproj @@ -47,7 +47,6 @@ Common\System\Reflection\MockParameterInfo.cs - @@ -174,6 +173,9 @@ Common\System\RandomDataGenerator.cs + + + @@ -265,4 +267,4 @@ - \ No newline at end of file + diff --git a/src/System.Runtime/tests/System/HashCodeTests.cs b/src/System.Runtime/tests/System/HashCodeTests.cs index de8acc608c1d..c1bfa3ad4dfa 100644 --- a/src/System.Runtime/tests/System/HashCodeTests.cs +++ b/src/System.Runtime/tests/System/HashCodeTests.cs @@ -27,22 +27,26 @@ public static class HashCodeTests public static void HashCode_Add(uint expected, params uint[] vector) { var hc = new HashCode(); - for (var i = 0; i < vector.Length; i++) + for (int i = 0; i < vector.Length; i++) hc.Add(vector[i]); - Assert.Equal(expected, unchecked((uint)hc.ToHashCode())); + Assert.Equal(expected, (uint)hc.ToHashCode()); } +# endif [Fact] public static void HashCode_Add_Generic() { var hc = new HashCode(); hc.Add(1); - hc.Add(100.2); + hc.Add(new ConstHashCodeType()); + + var expected = new HashCode(); + expected.Add(1); + expected.Add(ConstComparer.ConstantValue); - Assert.Equal(-273013950, hc.ToHashCode()); + Assert.Equal(expected.ToHashCode(), hc.ToHashCode()); } -# endif [Fact] public static void HashCode_Add_GenericEqualityComparer() @@ -52,16 +56,16 @@ public static void HashCode_Add_GenericEqualityComparer() hc.Add("Hello", new ConstComparer()); var expected = new HashCode(); - hc.Add(1); - hc.Add(ConstComparer.ConstantValue); + expected.Add(1); + expected.Add(ConstComparer.ConstantValue); - Assert.NotEqual(expected.ToHashCode(), hc.ToHashCode()); + Assert.Equal(expected.ToHashCode(), hc.ToHashCode()); } [Fact] public static void HashCode_Combine() { - var hcs = new[] + var hcs = new int[] { HashCode.Combine(1), HashCode.Combine(1, 2), @@ -82,8 +86,8 @@ public static void HashCode_Combine() HashCode.Combine(2, 3, 4, 5, 6, 7, 8, 9), }; - for (var i = 0; i < hcs.Length; i++) - for (var j = 0; j < hcs.Length; j++) + for (int i = 0; i < hcs.Length; i++) + for (int j = 0; j < hcs.Length; j++) { if (i == j) continue; Assert.NotEqual(hcs[i], hcs[j]); @@ -189,4 +193,9 @@ public class ConstComparer : System.Collections.Generic.IEqualityComparer false; public int GetHashCode(string obj) => ConstantValue; } + + public class ConstHashCodeType + { + public override int GetHashCode() => ConstComparer.ConstantValue; + } } From 32b83d767c9e544ff4131bde0df1bad4d0763557 Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Fri, 3 Nov 2017 20:06:07 -0700 Subject: [PATCH 04/11] PR Feedback * Fix test issue. * Remove unnecessary optimization. * Default comparer to EqualityComparer.Default. * Add info on how test vectors are added. --- src/System.Runtime/src/System/HashCode.cs | 132 +++++++++--------- .../tests/System.Runtime.Tests.csproj | 4 +- ...deTests.cs => HashCodeTests.netcoreapp.cs} | 7 +- 3 files changed, 75 insertions(+), 68 deletions(-) rename src/System.Runtime/tests/System/{HashCodeTests.cs => HashCodeTests.netcoreapp.cs} (94%) diff --git a/src/System.Runtime/src/System/HashCode.cs b/src/System.Runtime/src/System/HashCode.cs index 05eb1916a746..8d5774d340d9 100644 --- a/src/System.Runtime/src/System/HashCode.cs +++ b/src/System.Runtime/src/System/HashCode.cs @@ -76,9 +76,9 @@ public static int Combine(T1 value1) uint hash = MixEmptyState(); hash += 4; - QueueRound(ref hash, hc1); + hash = QueueRound(hash, hc1); - MixFinal(ref hash); + hash = MixFinal(hash); return (int)hash; } @@ -91,10 +91,10 @@ public static int Combine(T1 value1, T2 value2) uint hash = MixEmptyState(); hash += 8; - QueueRound(ref hash, hc1); - QueueRound(ref hash, hc2); + hash = QueueRound(hash, hc1); + hash = QueueRound(hash, hc2); - MixFinal(ref hash); + hash = MixFinal(hash); return (int)hash; } @@ -108,11 +108,11 @@ public static int Combine(T1 value1, T2 value2, T3 value3) uint hash = MixEmptyState(); hash += 12; - QueueRound(ref hash, hc1); - QueueRound(ref hash, hc2); - QueueRound(ref hash, hc3); + hash = QueueRound(hash, hc1); + hash = QueueRound(hash, hc2); + hash = QueueRound(hash, hc3); - MixFinal(ref hash); + hash = MixFinal(hash); return (int)hash; } @@ -126,15 +126,15 @@ public static int Combine(T1 value1, T2 value2, T3 value3, T4 va Initialize(out uint v1, out uint v2, out uint v3, out uint v4); - Round(ref v1, hc1); - Round(ref v2, hc2); - Round(ref v3, hc3); - Round(ref v4, hc4); + v1 = Round(v1, hc1); + v2 = Round(v2, hc2); + v3 = Round(v3, hc3); + v4 = Round(v4, hc4); uint hash = MixState(v1, v2, v3, v4); hash += 16; - MixFinal(ref hash); + hash = MixFinal(hash); return (int)hash; } @@ -149,17 +149,17 @@ public static int Combine(T1 value1, T2 value2, T3 value3, T Initialize(out uint v1, out uint v2, out uint v3, out uint v4); - Round(ref v1, hc1); - Round(ref v2, hc2); - Round(ref v3, hc3); - Round(ref v4, hc4); + v1 = Round(v1, hc1); + v2 = Round(v2, hc2); + v3 = Round(v3, hc3); + v4 = Round(v4, hc4); uint hash = MixState(v1, v2, v3, v4); hash += 20; - QueueRound(ref hash, hc5); + hash = QueueRound(hash, hc5); - MixFinal(ref hash); + hash = MixFinal(hash); return (int)hash; } @@ -175,18 +175,18 @@ public static int Combine(T1 value1, T2 value2, T3 value Initialize(out uint v1, out uint v2, out uint v3, out uint v4); - Round(ref v1, hc1); - Round(ref v2, hc2); - Round(ref v3, hc3); - Round(ref v4, hc4); + v1 = Round(v1, hc1); + v2 = Round(v2, hc2); + v3 = Round(v3, hc3); + v4 = Round(v4, hc4); uint hash = MixState(v1, v2, v3, v4); hash += 24; - QueueRound(ref hash, hc5); - QueueRound(ref hash, hc6); + hash = QueueRound(hash, hc5); + hash = QueueRound(hash, hc6); - MixFinal(ref hash); + hash = MixFinal(hash); return (int)hash; } @@ -203,19 +203,19 @@ public static int Combine(T1 value1, T2 value2, T3 v Initialize(out uint v1, out uint v2, out uint v3, out uint v4); - Round(ref v1, hc1); - Round(ref v2, hc2); - Round(ref v3, hc3); - Round(ref v4, hc4); + v1 = Round(v1, hc1); + v2 = Round(v2, hc2); + v3 = Round(v3, hc3); + v4 = Round(v4, hc4); uint hash = MixState(v1, v2, v3, v4); hash += 28; - QueueRound(ref hash, hc5); - QueueRound(ref hash, hc6); - QueueRound(ref hash, hc7); + hash = QueueRound(hash, hc5); + hash = QueueRound(hash, hc6); + hash = QueueRound(hash, hc7); - MixFinal(ref hash); + hash = MixFinal(hash); return (int)hash; } @@ -233,20 +233,20 @@ public static int Combine(T1 value1, T2 value2, Initialize(out uint v1, out uint v2, out uint v3, out uint v4); - Round(ref v1, hc1); - Round(ref v2, hc2); - Round(ref v3, hc3); - Round(ref v4, hc4); + v1 = Round(v1, hc1); + v2 = Round(v2, hc2); + v3 = Round(v3, hc3); + v4 = Round(v4, hc4); - Round(ref v1, hc5); - Round(ref v2, hc6); - Round(ref v3, hc7); - Round(ref v4, hc8); + v1 = Round(v1, hc5); + v2 = Round(v2, hc6); + v3 = Round(v3, hc7); + v4 = Round(v4, hc8); uint hash = MixState(v1, v2, v3, v4); hash += 32; - MixFinal(ref hash); + hash = MixFinal(hash); return (int)hash; } @@ -257,26 +257,26 @@ private static uint Rol(uint value, int count) [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void Initialize(out uint v1, out uint v2, out uint v3, out uint v4) { - var seed = s_seed; - v1 = seed + Prime1 + Prime2; - v2 = seed + Prime2; - v3 = seed + 0; - v4 = seed - Prime1; + v1 = s_seed + Prime1 + Prime2; + v2 = s_seed + Prime2; + v3 = s_seed + 0; + v4 = s_seed - Prime1; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void Round(ref uint hash, uint input) + private static uint Round(uint hash, uint input) { hash += input * Prime2; hash = Rol(hash, 13); hash *= Prime1; + return hash; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void QueueRound(ref uint hash, uint queuedValue) + private static uint QueueRound(uint hash, uint queuedValue) { hash += queuedValue * Prime3; - hash = Rol(hash, 17) * Prime4; + return Rol(hash, 17) * Prime4; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -291,13 +291,14 @@ private static uint MixEmptyState() } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void MixFinal(ref uint hash) + private static uint MixFinal(uint hash) { hash ^= hash >> 15; hash *= Prime2; hash ^= hash >> 13; hash *= Prime3; hash ^= hash >> 16; + return hash; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -310,7 +311,7 @@ public void Add(T value) public void Add(T value, IEqualityComparer comparer) { if (comparer is null) - throw new ArgumentNullException(nameof(comparer)); + comparer = EqualityComparer.Default; Add(comparer.GetHashCode(value)); } @@ -347,22 +348,24 @@ private void Add(int value) // To see what's really going on here, have a look at the Combine methods. + // Switch can't be inlined. + if (position == 0) _queue1 = val; else if (position == 1) _queue2 = val; else if (position == 2) _queue3 = val; - else + else // == 3 { // length smaller than 4? if ((_length ^ position) == 0) Initialize(out _v1, out _v2, out _v3, out _v4); - Round(ref _v1, _queue1); - Round(ref _v2, _queue2); - Round(ref _v3, _queue3); - Round(ref _v4, val); + _v1 = Round(_v1, _queue1); + _v2 = Round(_v2, _queue2); + _v3 = Round(_v3, _queue3); + _v4 = Round(_v4, val); } // Throw for more than uint.MaxValue fields. @@ -387,20 +390,21 @@ public int ToHashCode() hash += _length * 4; // Mix what remains in the queue + // Switch can't be inlined right now, so use as few + // branches as possible instead. if (position > 0) { - QueueRound(ref hash, _queue1); + hash = QueueRound(hash, _queue1); if (position > 1) { - QueueRound(ref hash, _queue2); + hash = QueueRound(hash, _queue2); if (position > 2) - QueueRound(ref hash, _queue3); + hash = QueueRound(hash, _queue3); } } - MixFinal(ref hash); - + hash = MixFinal(hash); return (int)hash; } diff --git a/src/System.Runtime/tests/System.Runtime.Tests.csproj b/src/System.Runtime/tests/System.Runtime.Tests.csproj index 173377ea4451..2b444c717cce 100644 --- a/src/System.Runtime/tests/System.Runtime.Tests.csproj +++ b/src/System.Runtime/tests/System.Runtime.Tests.csproj @@ -173,10 +173,8 @@ Common\System\RandomDataGenerator.cs - - - + diff --git a/src/System.Runtime/tests/System/HashCodeTests.cs b/src/System.Runtime/tests/System/HashCodeTests.netcoreapp.cs similarity index 94% rename from src/System.Runtime/tests/System/HashCodeTests.cs rename to src/System.Runtime/tests/System/HashCodeTests.netcoreapp.cs index c1bfa3ad4dfa..9d5c3c166564 100644 --- a/src/System.Runtime/tests/System/HashCodeTests.cs +++ b/src/System.Runtime/tests/System/HashCodeTests.netcoreapp.cs @@ -7,8 +7,13 @@ public static class HashCodeTests { - # if SYSTEM_HASHCODE_TESTVECTORS + // These test vectors were created using https://asecuritysite.com/encryption/xxHash + // 1. Find the hash for "". + // 2. Find the hash for "abcd". ASCII "abcd" and bit convert to uint. + // 3. Find the hash for "abcd1234". ASCII [ "abcd", "1234"] and bit convert to 2 uints. + // n. Continue until "abcd0123efgh4567ijkl8901mnop2345qrst6789uvwx0123yzab". + [Theory] [InlineData(0x02cc5d05U)] [InlineData(0xa3643705U, 0x64636261U)] From 07879b40596422cece890ab9d4d99770d1269ac1 Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Fri, 17 Nov 2017 10:34:48 -0800 Subject: [PATCH 05/11] * Removing 3rd party notices. (#3) * Remove HashCode.cs --- THIRD-PARTY-NOTICES.TXT | 30 +- src/System.Runtime/src/System.Runtime.csproj | 3 +- src/System.Runtime/src/System/HashCode.cs | 432 ------------------- 3 files changed, 2 insertions(+), 463 deletions(-) delete mode 100644 src/System.Runtime/src/System/HashCode.cs diff --git a/THIRD-PARTY-NOTICES.TXT b/THIRD-PARTY-NOTICES.TXT index 152fb31b62b9..06055ff03ecd 100644 --- a/THIRD-PARTY-NOTICES.TXT +++ b/THIRD-PARTY-NOTICES.TXT @@ -1,4 +1,4 @@ -.NET Core uses third-party libraries or other resources that may be +.NET Core uses third-party libraries or other resources that may be distributed under licenses different than the .NET Core software. In the event that we accidentally failed to list a required notice, please @@ -224,31 +224,3 @@ descriptions are © 1997-2005 Sean Eron Anderson. The code and descriptions are distributed in the hope that they will be useful, but WITHOUT ANY WARRANTY and without even the implied warranty of merchantability or fitness for a particular purpose. - -License notice for xxHash -------------------------- - -xxHash Library -Copyright (c) 2012-2014, Yann Collet -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, this - list of conditions and the following disclaimer in the documentation and/or - other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/System.Runtime/src/System.Runtime.csproj b/src/System.Runtime/src/System.Runtime.csproj index eca130a190b1..0129b51f901f 100644 --- a/src/System.Runtime/src/System.Runtime.csproj +++ b/src/System.Runtime/src/System.Runtime.csproj @@ -18,7 +18,6 @@ - @@ -34,4 +33,4 @@ - + \ No newline at end of file diff --git a/src/System.Runtime/src/System/HashCode.cs b/src/System.Runtime/src/System/HashCode.cs deleted file mode 100644 index 8d5774d340d9..000000000000 --- a/src/System.Runtime/src/System/HashCode.cs +++ /dev/null @@ -1,432 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -/* - -The xxHash32 implementation is based on the code published by Yann Collet: -https://raw.githubusercontent.com/Cyan4973/xxHash/5c174cfa4e45a42f94082dc0d4539b39696afea1/xxhash.c - - xxHash - Fast Hash algorithm - Copyright (C) 2012-2016, Yann Collet - - BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are - met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following disclaimer - in the documentation and/or other materials provided with the - distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - You can contact the author at : - - xxHash homepage: http://www.xxhash.com - - xxHash source repository : https://github.com/Cyan4973/xxHash - -*/ - -using System.Collections.Generic; -using System.ComponentModel; -using System.Runtime.CompilerServices; - -namespace System -{ - // xxHash32 is used for the hash code. - // https://github.com/Cyan4973/xxHash - - public struct HashCode - { -# if SYSTEM_HASHCODE_TESTVECTORS - private static readonly uint s_seed = 0; -# else - private static readonly uint s_seed = (uint)new Random().Next(int.MinValue, int.MaxValue); -# endif - - private const uint Prime1 = 2654435761U; - private const uint Prime2 = 2246822519U; - private const uint Prime3 = 3266489917U; - private const uint Prime4 = 668265263U; - private const uint Prime5 = 374761393U; - - private uint _v1, _v2, _v3, _v4; - private uint _queue1, _queue2, _queue3; - private uint _length; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Combine(T1 value1) - { - var hc1 = (uint)(value1?.GetHashCode() ?? 0); - - uint hash = MixEmptyState(); - hash += 4; - - hash = QueueRound(hash, hc1); - - hash = MixFinal(hash); - return (int)hash; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Combine(T1 value1, T2 value2) - { - var hc1 = (uint)(value1?.GetHashCode() ?? 0); - var hc2 = (uint)(value2?.GetHashCode() ?? 0); - - uint hash = MixEmptyState(); - hash += 8; - - hash = QueueRound(hash, hc1); - hash = QueueRound(hash, hc2); - - hash = MixFinal(hash); - return (int)hash; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Combine(T1 value1, T2 value2, T3 value3) - { - var hc1 = (uint)(value1?.GetHashCode() ?? 0); - var hc2 = (uint)(value2?.GetHashCode() ?? 0); - var hc3 = (uint)(value3?.GetHashCode() ?? 0); - - uint hash = MixEmptyState(); - hash += 12; - - hash = QueueRound(hash, hc1); - hash = QueueRound(hash, hc2); - hash = QueueRound(hash, hc3); - - hash = MixFinal(hash); - return (int)hash; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4) - { - var hc1 = (uint)(value1?.GetHashCode() ?? 0); - var hc2 = (uint)(value2?.GetHashCode() ?? 0); - var hc3 = (uint)(value3?.GetHashCode() ?? 0); - var hc4 = (uint)(value4?.GetHashCode() ?? 0); - - Initialize(out uint v1, out uint v2, out uint v3, out uint v4); - - v1 = Round(v1, hc1); - v2 = Round(v2, hc2); - v3 = Round(v3, hc3); - v4 = Round(v4, hc4); - - uint hash = MixState(v1, v2, v3, v4); - hash += 16; - - hash = MixFinal(hash); - return (int)hash; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5) - { - var hc1 = (uint)(value1?.GetHashCode() ?? 0); - var hc2 = (uint)(value2?.GetHashCode() ?? 0); - var hc3 = (uint)(value3?.GetHashCode() ?? 0); - var hc4 = (uint)(value4?.GetHashCode() ?? 0); - var hc5 = (uint)(value5?.GetHashCode() ?? 0); - - Initialize(out uint v1, out uint v2, out uint v3, out uint v4); - - v1 = Round(v1, hc1); - v2 = Round(v2, hc2); - v3 = Round(v3, hc3); - v4 = Round(v4, hc4); - - uint hash = MixState(v1, v2, v3, v4); - hash += 20; - - hash = QueueRound(hash, hc5); - - hash = MixFinal(hash); - return (int)hash; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6) - { - var hc1 = (uint)(value1?.GetHashCode() ?? 0); - var hc2 = (uint)(value2?.GetHashCode() ?? 0); - var hc3 = (uint)(value3?.GetHashCode() ?? 0); - var hc4 = (uint)(value4?.GetHashCode() ?? 0); - var hc5 = (uint)(value5?.GetHashCode() ?? 0); - var hc6 = (uint)(value6?.GetHashCode() ?? 0); - - Initialize(out uint v1, out uint v2, out uint v3, out uint v4); - - v1 = Round(v1, hc1); - v2 = Round(v2, hc2); - v3 = Round(v3, hc3); - v4 = Round(v4, hc4); - - uint hash = MixState(v1, v2, v3, v4); - hash += 24; - - hash = QueueRound(hash, hc5); - hash = QueueRound(hash, hc6); - - hash = MixFinal(hash); - return (int)hash; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7) - { - var hc1 = (uint)(value1?.GetHashCode() ?? 0); - var hc2 = (uint)(value2?.GetHashCode() ?? 0); - var hc3 = (uint)(value3?.GetHashCode() ?? 0); - var hc4 = (uint)(value4?.GetHashCode() ?? 0); - var hc5 = (uint)(value5?.GetHashCode() ?? 0); - var hc6 = (uint)(value6?.GetHashCode() ?? 0); - var hc7 = (uint)(value7?.GetHashCode() ?? 0); - - Initialize(out uint v1, out uint v2, out uint v3, out uint v4); - - v1 = Round(v1, hc1); - v2 = Round(v2, hc2); - v3 = Round(v3, hc3); - v4 = Round(v4, hc4); - - uint hash = MixState(v1, v2, v3, v4); - hash += 28; - - hash = QueueRound(hash, hc5); - hash = QueueRound(hash, hc6); - hash = QueueRound(hash, hc7); - - hash = MixFinal(hash); - return (int)hash; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7, T8 value8) - { - var hc1 = (uint)(value1?.GetHashCode() ?? 0); - var hc2 = (uint)(value2?.GetHashCode() ?? 0); - var hc3 = (uint)(value3?.GetHashCode() ?? 0); - var hc4 = (uint)(value4?.GetHashCode() ?? 0); - var hc5 = (uint)(value5?.GetHashCode() ?? 0); - var hc6 = (uint)(value6?.GetHashCode() ?? 0); - var hc7 = (uint)(value7?.GetHashCode() ?? 0); - var hc8 = (uint)(value8?.GetHashCode() ?? 0); - - Initialize(out uint v1, out uint v2, out uint v3, out uint v4); - - v1 = Round(v1, hc1); - v2 = Round(v2, hc2); - v3 = Round(v3, hc3); - v4 = Round(v4, hc4); - - v1 = Round(v1, hc5); - v2 = Round(v2, hc6); - v3 = Round(v3, hc7); - v4 = Round(v4, hc8); - - uint hash = MixState(v1, v2, v3, v4); - hash += 32; - - hash = MixFinal(hash); - return (int)hash; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint Rol(uint value, int count) - => (value << count) | (value >> (32 - count)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void Initialize(out uint v1, out uint v2, out uint v3, out uint v4) - { - v1 = s_seed + Prime1 + Prime2; - v2 = s_seed + Prime2; - v3 = s_seed + 0; - v4 = s_seed - Prime1; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint Round(uint hash, uint input) - { - hash += input * Prime2; - hash = Rol(hash, 13); - hash *= Prime1; - return hash; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint QueueRound(uint hash, uint queuedValue) - { - hash += queuedValue * Prime3; - return Rol(hash, 17) * Prime4; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint MixState(uint v1, uint v2, uint v3, uint v4) - { - return Rol(v1, 1) + Rol(v2, 7) + Rol(v3, 12) + Rol(v4, 18); - } - - private static uint MixEmptyState() - { - return s_seed + Prime5; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint MixFinal(uint hash) - { - hash ^= hash >> 15; - hash *= Prime2; - hash ^= hash >> 13; - hash *= Prime3; - hash ^= hash >> 16; - return hash; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Add(T value) - { - Add(value?.GetHashCode() ?? 0); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Add(T value, IEqualityComparer comparer) - { - if (comparer is null) - comparer = EqualityComparer.Default; - Add(comparer.GetHashCode(value)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void Add(int value) - { - // Note that x & 0x3 is like mod 3, but faster. - - var val = (uint)value; - uint position = _length & 0x3; - - // xxHash works as follows: - // 0. Initialize immediately. We can't do this in a struct (no default - // ctor). - // 1. Accumulate blocks of length 16 (4 uints) into 4 accumulators. - // 2. Accumulate remaining blocks of length 4 (1 uint) into the hash. - // 3. Accumulate remaining blocks of length 1 into the hash. - - // There is no need for *3 as this type only accepts ints. _queue1, - // _queue2 and _queue3 are basically a buffer so that when ToHashCode is - // called we can execute *2 correctly. That's what first three case - // statements do. - - // We need to initialize the xxHash32 state (_v1 -> _v4) lazily (see *0) - // and the last place that can be done if you look at the original code - // is just before the first block of 16 bytes is mixed in. The xxHash32 - // state is never used for streams containing fewer than 16 bytes. - - // A bloom filter is used to determine whether the default case statement - // has even been executed. To do that we check if the length is smaller - // than 4 (_length ^ position will be non-zero if this is the case). The - // case statement is for values larger than 2, so the check only succeeds - // on exactly 3. - - // To see what's really going on here, have a look at the Combine methods. - - // Switch can't be inlined. - - if (position == 0) - _queue1 = val; - else if (position == 1) - _queue2 = val; - else if (position == 2) - _queue3 = val; - else // == 3 - { - // length smaller than 4? - if ((_length ^ position) == 0) - Initialize(out _v1, out _v2, out _v3, out _v4); - - _v1 = Round(_v1, _queue1); - _v2 = Round(_v2, _queue2); - _v3 = Round(_v3, _queue3); - _v4 = Round(_v4, val); - } - - // Throw for more than uint.MaxValue fields. - _length = checked(_length + 1); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int ToHashCode() - { - uint position = _length & 0x3; - - // If the length is less than 3, _v1 -> _v4 don't contain - // anything yet. xxHash32 treats this differently. - - uint hash = (_length ^ position) == 0 - ? MixEmptyState() - : MixState(_v1, _v2, _v3, _v4); - - // Multiply by 4 because we've been counting in ints, not - // bytes. - - hash += _length * 4; - - // Mix what remains in the queue - // Switch can't be inlined right now, so use as few - // branches as possible instead. - - if (position > 0) - { - hash = QueueRound(hash, _queue1); - if (position > 1) - { - hash = QueueRound(hash, _queue2); - if (position > 2) - hash = QueueRound(hash, _queue3); - } - } - - hash = MixFinal(hash); - return (int)hash; - } - -# pragma warning disable 0809 - // Obsolete member 'memberA' overrides non-obsolete member 'memberB'. - // Disallowing GetHashCode is by design - - // * We decided to not override GetHashCode() to produce the hash code - // as this would be weird, both naming-wise as well as from a behavioral - // standpoint (GetHashCode() should return the object's hash code, not - // the one being computed). - - // * Even though ToHashCode() can be called safely multiple times on this - // implementation, it is not part of the contract. If the implementation - // has to change in the future we don't want to worry about people who - // might have incorrectly used this type. - - [Obsolete("Use ToHashCode to retrieve the computed hash code.", error: true)] - [EditorBrowsable(EditorBrowsableState.Never)] - [MethodImpl(MethodImplOptions.NoInlining)] - public override int GetHashCode() => throw new NotSupportedException(); -# pragma warning restore 0809 - - } -} From 4c45da502a7469a947bdd29399d3e5f5cc9e4466 Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Sun, 3 Dec 2017 18:42:24 -0800 Subject: [PATCH 06/11] PR Feedback --- src/System.Runtime/ref/System.Runtime.cs | 20 ++++++------------- .../tests/System/HashCodeTests.netcoreapp.cs | 17 ++++++++++++++++ 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/System.Runtime/ref/System.Runtime.cs b/src/System.Runtime/ref/System.Runtime.cs index ef556175bea3..58ff8f7f32b7 100644 --- a/src/System.Runtime/ref/System.Runtime.cs +++ b/src/System.Runtime/ref/System.Runtime.cs @@ -3678,47 +3678,39 @@ public void SetTarget(T target) { } public partial struct HashCode { - [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public static int Combine(T1 value1) { throw null; } - [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public static int Combine(T1 value1, T2 value2) { throw null; } - [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public static int Combine(T1 value1, T2 value2, T3 value3) { throw null; } - [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4) { throw null; } - [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5) { throw null; } - [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6) { throw null; } - [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7) { throw null; } - [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7, T8 value8) { throw null; } - [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public void Add(T value) { } - [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public void Add(T value, System.Collections.Generic.IEqualityComparer comparer) { } - [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public int ToHashCode() { throw null; } -# pragma warning disable 0809 +#pragma warning disable 0809 - [System.ObsoleteAttribute("Use ToHashCode to retrieve the computed hash code.", error: true)] + [System.ObsoleteAttribute("HashCode is a mutable struct and should not be compared with other HashCodes. Use ToHashCode to retrieve the computed hash code.", error: true)] [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public override int GetHashCode() { throw null; } -# pragma warning restore 0809 + [System.ObsoleteAttribute("HashCode is a mutable struct and should not be compared with other HashCodes.", error: true)] + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + public override bool Equals(object obj) { throw null; } +#pragma warning restore 0809 } } diff --git a/src/System.Runtime/tests/System/HashCodeTests.netcoreapp.cs b/src/System.Runtime/tests/System/HashCodeTests.netcoreapp.cs index 9d5c3c166564..9596e1f554ac 100644 --- a/src/System.Runtime/tests/System/HashCodeTests.netcoreapp.cs +++ b/src/System.Runtime/tests/System/HashCodeTests.netcoreapp.cs @@ -191,6 +191,23 @@ public static void HashCode_Combine_Add_8() Assert.Equal(hc.ToHashCode(), HashCode.Combine(1, 2, 3, 4, 5, 6, 7, 8)); } + [Fact] + public static void HashCode_GetHashCode() + { + var hc = new HashCode(); + + Assert.Throws(() => hc.GetHashCode()); + } + + + [Fact] + public static void HashCode_Equals() + { + var hc = new HashCode(); + + Assert.Throws(() => hc.Equals(hc)); + } + public class ConstComparer : System.Collections.Generic.IEqualityComparer { public const int ConstantValue = 1234; From 20913b770e4f25ef1518b9bbba0fe7aa9918bade Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Tue, 5 Dec 2017 13:43:12 -0800 Subject: [PATCH 07/11] PR Feedback --- src/System.Runtime/tests/System/HashCodeTests.netcoreapp.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/System.Runtime/tests/System/HashCodeTests.netcoreapp.cs b/src/System.Runtime/tests/System/HashCodeTests.netcoreapp.cs index 9596e1f554ac..c2feda8463bf 100644 --- a/src/System.Runtime/tests/System/HashCodeTests.netcoreapp.cs +++ b/src/System.Runtime/tests/System/HashCodeTests.netcoreapp.cs @@ -7,7 +7,7 @@ public static class HashCodeTests { -# if SYSTEM_HASHCODE_TESTVECTORS +#if SYSTEM_HASHCODE_TESTVECTORS // These test vectors were created using https://asecuritysite.com/encryption/xxHash // 1. Find the hash for "". // 2. Find the hash for "abcd". ASCII "abcd" and bit convert to uint. @@ -37,7 +37,7 @@ public static void HashCode_Add(uint expected, params uint[] vector) Assert.Equal(expected, (uint)hc.ToHashCode()); } -# endif +#endif [Fact] public static void HashCode_Add_Generic() From a9a6d7ffa8e10c50e57e2cb63641e65e8a6262ba Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Tue, 5 Dec 2017 13:55:07 -0800 Subject: [PATCH 08/11] Adding documentation above HashCode_Add. --- src/System.Runtime/tests/System/HashCodeTests.netcoreapp.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/System.Runtime/tests/System/HashCodeTests.netcoreapp.cs b/src/System.Runtime/tests/System/HashCodeTests.netcoreapp.cs index c2feda8463bf..e2de0c1ce71a 100644 --- a/src/System.Runtime/tests/System/HashCodeTests.netcoreapp.cs +++ b/src/System.Runtime/tests/System/HashCodeTests.netcoreapp.cs @@ -14,6 +14,10 @@ public static class HashCodeTests // 3. Find the hash for "abcd1234". ASCII [ "abcd", "1234"] and bit convert to 2 uints. // n. Continue until "abcd0123efgh4567ijkl8901mnop2345qrst6789uvwx0123yzab". + // HashCode is not deterministic across AppDomains by design. This means that + // these tests can not be executed against the version that exists within + // CoreCLR. Copy HashCode and set m_seed to 0 in order to execute these tests. + [Theory] [InlineData(0x02cc5d05U)] [InlineData(0xa3643705U, 0x64636261U)] From 638b55b06296bfa4be828c64175387b15bfa28fc Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Wed, 6 Dec 2017 12:32:20 -0800 Subject: [PATCH 09/11] Additional unit tests --- .../tests/System/HashCodeTests.netcoreapp.cs | 103 ++++++++++++------ 1 file changed, 70 insertions(+), 33 deletions(-) diff --git a/src/System.Runtime/tests/System/HashCodeTests.netcoreapp.cs b/src/System.Runtime/tests/System/HashCodeTests.netcoreapp.cs index e2de0c1ce71a..a94c968bd512 100644 --- a/src/System.Runtime/tests/System/HashCodeTests.netcoreapp.cs +++ b/src/System.Runtime/tests/System/HashCodeTests.netcoreapp.cs @@ -7,41 +7,65 @@ public static class HashCodeTests { -#if SYSTEM_HASHCODE_TESTVECTORS - // These test vectors were created using https://asecuritysite.com/encryption/xxHash - // 1. Find the hash for "". - // 2. Find the hash for "abcd". ASCII "abcd" and bit convert to uint. - // 3. Find the hash for "abcd1234". ASCII [ "abcd", "1234"] and bit convert to 2 uints. - // n. Continue until "abcd0123efgh4567ijkl8901mnop2345qrst6789uvwx0123yzab". - - // HashCode is not deterministic across AppDomains by design. This means that - // these tests can not be executed against the version that exists within - // CoreCLR. Copy HashCode and set m_seed to 0 in order to execute these tests. - - [Theory] - [InlineData(0x02cc5d05U)] - [InlineData(0xa3643705U, 0x64636261U)] - [InlineData(0x4603e94cU, 0x64636261U, 0x33323130U)] - [InlineData(0xd8a1e80fU, 0x64636261U, 0x33323130U, 0x68676665U)] - [InlineData(0x4b62a7cfU, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U)] - [InlineData(0xc33a7641U, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U, 0x6c6b6a69U)] - [InlineData(0x1a794705U, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U, 0x6c6b6a69U, 0x31303938U)] - [InlineData(0x4d79177dU, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U, 0x6c6b6a69U, 0x31303938U, 0x706f6e6dU)] - [InlineData(0x59d79205U, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U, 0x6c6b6a69U, 0x31303938U, 0x706f6e6dU, 0x35343332U)] - [InlineData(0x49585aaeU, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U, 0x6c6b6a69U, 0x31303938U, 0x706f6e6dU, 0x35343332U, 0x74737271U)] - [InlineData(0x2f005ff1U, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U, 0x6c6b6a69U, 0x31303938U, 0x706f6e6dU, 0x35343332U, 0x74737271U, 0x39383736U)] - [InlineData(0x0ce339bdU, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U, 0x6c6b6a69U, 0x31303938U, 0x706f6e6dU, 0x35343332U, 0x74737271U, 0x39383736U, 0x78777675U)] - [InlineData(0xb31bd2ffU, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U, 0x6c6b6a69U, 0x31303938U, 0x706f6e6dU, 0x35343332U, 0x74737271U, 0x39383736U, 0x78777675U, 0x33323130U)] - [InlineData(0xa821efa3U, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U, 0x6c6b6a69U, 0x31303938U, 0x706f6e6dU, 0x35343332U, 0x74737271U, 0x39383736U, 0x78777675U, 0x33323130U, 0x62617a79U)] - public static void HashCode_Add(uint expected, params uint[] vector) + [Fact] + public static void HashCode_Add() { - var hc = new HashCode(); - for (int i = 0; i < vector.Length; i++) - hc.Add(vector[i]); + // The version of xUnit used by corefx does not support params theories. + void Theory(uint expected, params uint[] vector) + { + var hc = new HashCode(); + for (int i = 0; i < vector.Length; i++) + hc.Add(vector[i]); - Assert.Equal(expected, (uint)hc.ToHashCode()); - } +#if SYSTEM_HASHCODE_TESTVECTORS + // HashCode is not deterministic across AppDomains by design. This means + // that these tests can not be executed against the version that exists + // within CoreCLR. Copy HashCode and set m_seed to 0 in order to execute + // these tests. + + Assert.Equal(expected, (uint)hc.ToHashCode()); +#else + // Validate that the HashCode.m_seed is randomized. This has a 1 in 4 + // billion chance of resulting in a false negative, as HashCode.m_seed + // can be 0. + + Assert.NotEqual(expected, (uint)hc.ToHashCode()); #endif + } + + // These test vectors were created using https://asecuritysite.com/encryption/xxHash + // 1. Find the hash for "". + // 2. Find the hash for "abcd". ASCII "abcd" and bit convert to uint. + // 3. Find the hash for "abcd1234". ASCII [ "abcd", "1234"] and bit convert to 2 uints. + // n. Continue until "abcd0123efgh4567ijkl8901mnop2345qrst6789uvwx0123yzab". + + Theory(0x02cc5d05U); + Theory(0xa3643705U, 0x64636261U ); + Theory(0x4603e94cU, 0x64636261U, 0x33323130U ); + Theory(0xd8a1e80fU, 0x64636261U, 0x33323130U, 0x68676665U ); + Theory(0x4b62a7cfU, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U ); + Theory(0xc33a7641U, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U, 0x6c6b6a69U ); + Theory(0x1a794705U, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U, 0x6c6b6a69U, 0x31303938U ); + Theory(0x4d79177dU, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U, 0x6c6b6a69U, 0x31303938U, 0x706f6e6dU ); + Theory(0x59d79205U, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U, 0x6c6b6a69U, 0x31303938U, 0x706f6e6dU, 0x35343332U ); + Theory(0x49585aaeU, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U, 0x6c6b6a69U, 0x31303938U, 0x706f6e6dU, 0x35343332U, 0x74737271U ); + Theory(0x2f005ff1U, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U, 0x6c6b6a69U, 0x31303938U, 0x706f6e6dU, 0x35343332U, 0x74737271U, 0x39383736U ); + Theory(0x0ce339bdU, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U, 0x6c6b6a69U, 0x31303938U, 0x706f6e6dU, 0x35343332U, 0x74737271U, 0x39383736U, 0x78777675U ); + Theory(0xb31bd2ffU, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U, 0x6c6b6a69U, 0x31303938U, 0x706f6e6dU, 0x35343332U, 0x74737271U, 0x39383736U, 0x78777675U, 0x33323130U ); + Theory(0xa821efa3U, 0x64636261U, 0x33323130U, 0x68676665U, 0x37363534U, 0x6c6b6a69U, 0x31303938U, 0x706f6e6dU, 0x35343332U, 0x74737271U, 0x39383736U, 0x78777675U, 0x33323130U, 0x62617a79U ); + } + + [Fact] + public static void HashCode_Add_HashCode() + { + var hc1 = new HashCode(); + hc1.Add("Hello"); + + var hc2 = new HashCode(); + hc2.Add("Hello".GetHashCode()); + + Assert.Equal(hc1.ToHashCode(), hc2.ToHashCode()); + } [Fact] public static void HashCode_Add_Generic() @@ -71,6 +95,20 @@ public static void HashCode_Add_GenericEqualityComparer() Assert.Equal(expected.ToHashCode(), hc.ToHashCode()); } + [Fact] + public static void HashCode_Add_NullEqualityComparer() + { + var hc = new HashCode(); + hc.Add(1); + hc.Add("Hello", null); + + var expected = new HashCode(); + expected.Add(1); + expected.Add("Hello"); + + Assert.Equal(expected.ToHashCode(), hc.ToHashCode()); + } + [Fact] public static void HashCode_Combine() { @@ -203,7 +241,6 @@ public static void HashCode_GetHashCode() Assert.Throws(() => hc.GetHashCode()); } - [Fact] public static void HashCode_Equals() { From 4b0ca465785d0ba87fb6dac8b7e6dfaf2420780e Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Wed, 6 Dec 2017 17:58:20 -0800 Subject: [PATCH 10/11] Additional tests --- .../tests/System/HashCodeTests.netcoreapp.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/System.Runtime/tests/System/HashCodeTests.netcoreapp.cs b/src/System.Runtime/tests/System/HashCodeTests.netcoreapp.cs index a94c968bd512..06f08cd3c7f9 100644 --- a/src/System.Runtime/tests/System/HashCodeTests.netcoreapp.cs +++ b/src/System.Runtime/tests/System/HashCodeTests.netcoreapp.cs @@ -81,6 +81,18 @@ public static void HashCode_Add_Generic() Assert.Equal(expected.ToHashCode(), hc.ToHashCode()); } + [Fact] + public static void HashCode_Add_Null() + { + var hc = new HashCode(); + hc.Add(null); + + var expected = new HashCode(); + expected.Add(EqualityComparer.Default.GetHashCode(null)); + + Assert.Equal(expected.ToHashCode(), hc.ToHashCode()); + } + [Fact] public static void HashCode_Add_GenericEqualityComparer() { @@ -249,6 +261,24 @@ public static void HashCode_Equals() Assert.Throws(() => hc.Equals(hc)); } + [Fact] + public static void HashCode_GetHashCode_Boxed() + { + var hc = new HashCode(); + var obj = (object)hc; + + Assert.Throws(() => obj.GetHashCode()); + } + + [Fact] + public static void HashCode_Equals_Boxed() + { + var hc = new HashCode(); + var obj = (object)hc; + + Assert.Throws(() => obj.Equals(obj)); + } + public class ConstComparer : System.Collections.Generic.IEqualityComparer { public const int ConstantValue = 1234; From 07aa81d1c4ab584d930ef18be55e2999f979f10c Mon Sep 17 00:00:00 2001 From: Jonathan Dickinson Date: Wed, 6 Dec 2017 18:09:02 -0800 Subject: [PATCH 11/11] Fix compilation issue. --- src/System.Runtime/tests/System/HashCodeTests.netcoreapp.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/System.Runtime/tests/System/HashCodeTests.netcoreapp.cs b/src/System.Runtime/tests/System/HashCodeTests.netcoreapp.cs index 06f08cd3c7f9..80c93f7de06e 100644 --- a/src/System.Runtime/tests/System/HashCodeTests.netcoreapp.cs +++ b/src/System.Runtime/tests/System/HashCodeTests.netcoreapp.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using Xunit; public static class HashCodeTests