Created
June 17, 2017 18:18
-
-
Save tannergooding/89bd72f05ab772bfe5ad3a03d6493650 to your computer and use it in GitHub Desktop.
System.HashCode implementation for https://github.com/dotnet/corefx/issues/14354
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections.Generic; | |
using System.ComponentModel; | |
using System.Runtime.CompilerServices; | |
namespace System | |
{ | |
public struct HashCode | |
{ | |
private int _bytesCombined; | |
private int _combinedValue; | |
public void Add<T>(T value) | |
{ | |
_combinedValue = CombineValue(value.GetHashCode(), _combinedValue); | |
_bytesCombined += sizeof(int); | |
} | |
public void Add<T>(T value, IEqualityComparer<T> comparer) | |
{ | |
_combinedValue = CombineValue(comparer.GetHashCode(value), _combinedValue); | |
_bytesCombined += sizeof(int); | |
} | |
[Obsolete("Use ToHashCode to retrieve the computed hash code.", error: true)] | |
[EditorBrowsable(EditorBrowsableState.Never)] | |
#pragma warning disable CS0809 // Obsolete member overrides non-obsolete member | |
public override int GetHashCode() | |
#pragma warning restore CS0809 // Obsolete member overrides non-obsolete member | |
{ | |
throw new NotImplementedException(); | |
} | |
public int ToHashCode() | |
{ | |
return FinalizeValue(_combinedValue, _bytesCombined); | |
} | |
#region Static Methods | |
// No need to actually instantiate an instance of HashCode for these methods. The number of bytes combined | |
// is constant per method (sizeof(int) * number of inputs) and the combined value can be a local. | |
public static int Combine<T1>(T1 value1) | |
{ | |
return FinalizeValue(value1.GetHashCode(), sizeof(int)); | |
} | |
public static int Combine<T1, T2>(T1 value1, T2 value2) | |
{ | |
var combinedValue = CombineValue(value2.GetHashCode(), value1.GetHashCode()); | |
return FinalizeValue(combinedValue, sizeof(int) * 2); | |
} | |
public static int Combine<T1, T2, T3>(T1 value1, T2 value2, T3 value3) | |
{ | |
var combinedValue = CombineValue(value2.GetHashCode(), value1.GetHashCode()); | |
combinedValue = CombineValue(value3.GetHashCode(), combinedValue); | |
return FinalizeValue(combinedValue, sizeof(int) * 3); | |
} | |
public static int Combine<T1, T2, T3, T4>(T1 value1, T2 value2, T3 value3, T4 value4) | |
{ | |
var combinedValue = CombineValue(value2.GetHashCode(), value1.GetHashCode()); | |
combinedValue = CombineValue(value3.GetHashCode(), combinedValue); | |
combinedValue = CombineValue(value4.GetHashCode(), combinedValue); | |
return FinalizeValue(combinedValue, sizeof(int) * 4); | |
} | |
public static int Combine<T1, T2, T3, T4, T5>(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5) | |
{ | |
var combinedValue = CombineValue(value2.GetHashCode(), value1.GetHashCode()); | |
combinedValue = CombineValue(value3.GetHashCode(), combinedValue); | |
combinedValue = CombineValue(value4.GetHashCode(), combinedValue); | |
combinedValue = CombineValue(value5.GetHashCode(), combinedValue); | |
return FinalizeValue(combinedValue, sizeof(int) * 5); | |
} | |
public static int Combine<T1, T2, T3, T4, T5, T6>(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6) | |
{ | |
var combinedValue = CombineValue(value2.GetHashCode(), value1.GetHashCode()); | |
combinedValue = CombineValue(value3.GetHashCode(), combinedValue); | |
combinedValue = CombineValue(value4.GetHashCode(), combinedValue); | |
combinedValue = CombineValue(value5.GetHashCode(), combinedValue); | |
combinedValue = CombineValue(value6.GetHashCode(), combinedValue); | |
return FinalizeValue(combinedValue, sizeof(int) * 6); | |
} | |
public static int Combine<T1, T2, T3, T4, T5, T6, T7>(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7) | |
{ | |
var combinedValue = CombineValue(value2.GetHashCode(), value1.GetHashCode()); | |
combinedValue = CombineValue(value3.GetHashCode(), combinedValue); | |
combinedValue = CombineValue(value4.GetHashCode(), combinedValue); | |
combinedValue = CombineValue(value5.GetHashCode(), combinedValue); | |
combinedValue = CombineValue(value6.GetHashCode(), combinedValue); | |
combinedValue = CombineValue(value7.GetHashCode(), combinedValue); | |
return FinalizeValue(combinedValue, sizeof(int) * 7); | |
} | |
public static int Combine<T1, T2, T3, T4, T5, T6, T7, T8>(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7, T8 value8) | |
{ | |
var combinedValue = CombineValue(value2.GetHashCode(), value1.GetHashCode()); | |
combinedValue = CombineValue(value3.GetHashCode(), combinedValue); | |
combinedValue = CombineValue(value4.GetHashCode(), combinedValue); | |
combinedValue = CombineValue(value5.GetHashCode(), combinedValue); | |
combinedValue = CombineValue(value6.GetHashCode(), combinedValue); | |
combinedValue = CombineValue(value7.GetHashCode(), combinedValue); | |
combinedValue = CombineValue(value8.GetHashCode(), combinedValue); | |
return FinalizeValue(combinedValue, sizeof(int) * 8); | |
} | |
#endregion | |
#region Helper Methods | |
private static int CombineValue(int value, int seed) | |
{ | |
var combinedHashCode = value; | |
unchecked | |
{ | |
combinedHashCode *= (-862048943); | |
combinedHashCode = unchecked((int)(RotateLeft(unchecked((uint)(combinedHashCode)), 15))); | |
combinedHashCode *= 461845907; | |
combinedHashCode ^= seed; | |
combinedHashCode = unchecked((int)(RotateLeft(unchecked((uint)(combinedHashCode)), 13))); | |
combinedHashCode *= 5; | |
combinedHashCode -= 430675100; | |
} | |
return combinedHashCode; | |
} | |
public static int FinalizeValue(int combinedValue, int bytesCombined) | |
{ | |
var finalizedHashCode = combinedValue; | |
unchecked | |
{ | |
finalizedHashCode ^= bytesCombined; | |
finalizedHashCode ^= (int)((uint)(finalizedHashCode) >> 16); | |
finalizedHashCode *= (-2048144789); | |
finalizedHashCode ^= (int)((uint)(finalizedHashCode) >> 13); | |
finalizedHashCode *= (-1028477387); | |
finalizedHashCode ^= (int)((uint)(finalizedHashCode) >> 16); | |
} | |
return finalizedHashCode; | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
private static uint RotateLeft(uint value, byte bits) | |
{ | |
// This pattern is recognized by the JIT and should be optimized | |
// to a ROL instruction rather than two shifts. | |
return unchecked((value << bits) | (value >> (32 - bits))); | |
} | |
#endregion | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Diagnostics; | |
class Program | |
{ | |
const int OuterIterations = 10000; | |
const int InnerIterations = 10000; | |
static void Main(string[] args) | |
{ | |
var throughputScale1 = MeasureThroughputScale(TestCombine1); | |
Console.WriteLine($"1: {throughputScale1 * (sizeof(int) * 1) / 1048576:F2} MB/s"); | |
var throughputScale2 = MeasureThroughputScale(TestCombine2); | |
Console.WriteLine($"2: {throughputScale2 * (sizeof(int) * 2) / 1048576:F2} MB/s"); | |
var throughputScale3 = MeasureThroughputScale(TestCombine3); | |
Console.WriteLine($"3: {throughputScale3 * (sizeof(int) * 3) / 1048576:F2} MB/s"); | |
var throughputScale4 = MeasureThroughputScale(TestCombine4); | |
Console.WriteLine($"4: {throughputScale4 * (sizeof(int) * 4) / 1048576:F2} MB/s"); | |
var throughputScale5 = MeasureThroughputScale(TestCombine5); | |
Console.WriteLine($"5: {throughputScale5 * (sizeof(int) * 5) / 1048576:F2} MB/s"); | |
var throughputScale6 = MeasureThroughputScale(TestCombine6); | |
Console.WriteLine($"6: {throughputScale6 * (sizeof(int) * 6) / 1048576:F2} MB/s"); | |
var throughputScale7 = MeasureThroughputScale(TestCombine7); | |
Console.WriteLine($"7: {throughputScale7 * (sizeof(int) * 7) / 1048576:F2} MB/s"); | |
var throughputScale8 = MeasureThroughputScale(TestCombine8); | |
Console.WriteLine($"8: {throughputScale8 * (sizeof(int) * 8) / 1048576:F2} MB/s"); | |
} | |
static double MeasureThroughputScale(Func<(double elapsed, int result)> func) | |
{ | |
var elapsed = 0.0; | |
func(); // Cold Test so that JIT overhead doesn't add too much noise | |
for (int i = 0; i < OuterIterations; i++) | |
{ | |
elapsed += func().elapsed; | |
} | |
var outerIterationTime = elapsed / OuterIterations; | |
var innerIterationTime = outerIterationTime / InnerIterations; | |
return (1.0 / innerIterationTime); | |
} | |
#region Tests | |
// We need to actually consume the output and modify the inputs to ensure the JIT doesn't perform | |
// dead-code optimization (I've not actually seen it do this outside of intrinsics, but better safe | |
// than sorry). | |
// Additionally, we just pass the previous result as the inputs for the next call. This simulates | |
// the values changing between each iteration but should also avoid too much noise from memory | |
// reads (as the result should be stored in RAX and is just copied to other registers or the local | |
// stack for the next call). | |
static (double, int) TestCombine1() | |
{ | |
var result = 0; | |
var start = Stopwatch.GetTimestamp(); | |
for (int i = 0; i< InnerIterations; i++) | |
{ | |
result = HashCode.Combine(result); | |
} | |
var stop = Stopwatch.GetTimestamp(); | |
var elapsed = (stop - start) / (double)(Stopwatch.Frequency); | |
return (elapsed, result); | |
} | |
static (double, int) TestCombine2() | |
{ | |
var result = 0; | |
var start = Stopwatch.GetTimestamp(); | |
for (int i = 0; i< InnerIterations; i++) | |
{ | |
result = HashCode.Combine(result, result); | |
} | |
var stop = Stopwatch.GetTimestamp(); | |
var elapsed = (stop - start) / (double)(Stopwatch.Frequency); | |
return (elapsed, result); | |
} | |
static (double, int) TestCombine3() | |
{ | |
var result = 0; | |
var start = Stopwatch.GetTimestamp(); | |
for (int i = 0; i< InnerIterations; i++) | |
{ | |
result = HashCode.Combine(result, result, result); | |
} | |
var stop = Stopwatch.GetTimestamp(); | |
var elapsed = (stop - start) / (double)(Stopwatch.Frequency); | |
return (elapsed, result); | |
} | |
static (double, int) TestCombine4() | |
{ | |
var result = 0; | |
var start = Stopwatch.GetTimestamp(); | |
for (int i = 0; i< InnerIterations; i++) | |
{ | |
result = HashCode.Combine(result, result, result, result); | |
} | |
var stop = Stopwatch.GetTimestamp(); | |
var elapsed = (stop - start) / (double)(Stopwatch.Frequency); | |
return (elapsed, result); | |
} | |
static (double, int) TestCombine5() | |
{ | |
var result = 0; | |
var start = Stopwatch.GetTimestamp(); | |
for (int i = 0; i< InnerIterations; i++) | |
{ | |
result = HashCode.Combine(result, result, result, result, result); | |
} | |
var stop = Stopwatch.GetTimestamp(); | |
var elapsed = (stop - start) / (double)(Stopwatch.Frequency); | |
return (elapsed, result); | |
} | |
static (double, int) TestCombine6() | |
{ | |
var result = 0; | |
var start = Stopwatch.GetTimestamp(); | |
for (int i = 0; i< InnerIterations; i++) | |
{ | |
result = HashCode.Combine(result, result, result, result, result, result); | |
} | |
var stop = Stopwatch.GetTimestamp(); | |
var elapsed = (stop - start) / (double)(Stopwatch.Frequency); | |
return (elapsed, result); | |
} | |
static (double, int) TestCombine7() | |
{ | |
var result = 0; | |
var start = Stopwatch.GetTimestamp(); | |
for (int i = 0; i< InnerIterations; i++) | |
{ | |
result = HashCode.Combine(result, result, result, result, result, result, result); | |
} | |
var stop = Stopwatch.GetTimestamp(); | |
var elapsed = (stop - start) / (double)(Stopwatch.Frequency); | |
return (elapsed, result); | |
} | |
static (double, int) TestCombine8() | |
{ | |
var result = 0; | |
var start = Stopwatch.GetTimestamp(); | |
for (int i = 0; i< InnerIterations; i++) | |
{ | |
result = HashCode.Combine(result, result, result, result, result, result, result, result); | |
} | |
var stop = Stopwatch.GetTimestamp(); | |
var elapsed = (stop - start) / (double)(Stopwatch.Frequency); | |
return (elapsed, result); | |
} | |
#endregion | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment