Better Tuple hashing, avoid boxing in Equals/GetHashCode (dotnet/coreclr#6767)
authorJames Ko <jamesqko@gmail.com>
Wed, 19 Oct 2016 20:24:01 +0000 (16:24 -0400)
committerJan Kotas <jkotas@microsoft.com>
Wed, 19 Oct 2016 20:24:01 +0000 (13:24 -0700)
Commit migrated from https://github.com/dotnet/coreclr/commit/361e6eb85c74b57691955d97dda801d725cd2a59

src/coreclr/src/mscorlib/mscorlib.shared.sources.props
src/coreclr/src/mscorlib/src/System/Numerics/Hashing/HashHelpers.cs [new file with mode: 0644]
src/coreclr/src/mscorlib/src/System/Tuple.cs

index 63c4e3f..3740622 100644 (file)
     <SafehandleSources Condition="'$(FeatureWin32Registry)' == 'true'" Include="$(BclSourcesRoot)\Microsoft\Win32\SafeHandles\SafeRegistryHandle.cs" />
   </ItemGroup>
   <ItemGroup>
+    <NumericsSources Include="$(BclSourcesRoot)\System\Numerics\Hashing\HashHelpers.cs" />
+  </ItemGroup>
+  <ItemGroup>
     <MscorlibSources Include="@(SystemSources)"/>
     <MscorlibSources Include="@(ThreadingSources)"/>
     <MscorlibSources Include="@(DeploymentSources)"/>
     <MscorlibSources Include="@(VersioningSources)"/>
     <MscorlibSources Include="@(DesignerServicesSources)"/>
     <MscorlibSources Include="@(InternalSources)"/>
+    <MscorlibSources Include="@(NumericsSources)"/>
     <MscorlibSources Include="$(BclSourcesRoot)\GlobalSuppressions.cs"/>
   </ItemGroup>
 </Project>
diff --git a/src/coreclr/src/mscorlib/src/System/Numerics/Hashing/HashHelpers.cs b/src/coreclr/src/mscorlib/src/System/Numerics/Hashing/HashHelpers.cs
new file mode 100644 (file)
index 0000000..0314d1a
--- /dev/null
@@ -0,0 +1,19 @@
+// 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.
+
+namespace System.Numerics.Hashing
+{
+    // Please change the corresponding file in corefx if this is changed.
+
+    internal static class HashHelpers
+    {
+        public static int Combine(int h1, int h2)
+        {
+            // The jit optimizes this to use the ROL instruction on x86
+            // Related GitHub pull request: dotnet/coreclr#1830
+            uint shift5 = ((uint)h1 << 5) | ((uint)h1 >> 27);
+            return ((int)shift5 + h1) ^ h2;
+        }
+    }
+}
index 99164bc..92903ec 100644 (file)
@@ -1,6 +1,7 @@
 // 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 System.Text;
 using System.Collections;
@@ -52,33 +53,46 @@ namespace System {
             return new Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8>>(item1, item2, item3, item4, item5, item6, item7, new Tuple<T8>(item8));
         }
 
-        // From System.Web.Util.HashCodeCombiner
-        internal static int CombineHashCodes(int h1, int h2) {
-            return (((h1 << 5) + h1) ^ h2);
-        }
-
-        internal static int CombineHashCodes(int h1, int h2, int h3) {
+        internal static int CombineHashCodes(int h1, int h2)
+        {
+            // SRP: Keep the actual hashing logic in a separate class
+            // Note if that class is updated, the corresponding file in corefx
+            // should be as well
+            return System.Numerics.Hashing.HashHelpers.Combine(h1, h2);
+        }
+        
+        // These overloads mirror the ones in corefx/ValueTuple.
+        // We combine the hashes sequentially instead of in chunks,
+        // which results in simpler logic and a better spreading effect.
+        
+        internal static int CombineHashCodes(int h1, int h2, int h3)
+        {
             return CombineHashCodes(CombineHashCodes(h1, h2), h3);
         }
-
-        internal static int CombineHashCodes(int h1, int h2, int h3, int h4) {
-            return CombineHashCodes(CombineHashCodes(h1, h2), CombineHashCodes(h3, h4));
+        
+        internal static int CombineHashCodes(int h1, int h2, int h3, int h4)
+        {
+            return CombineHashCodes(CombineHashCodes(h1, h2, h3), h4);
         }
-
-        internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5) {
+        
+        internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5)
+        {
             return CombineHashCodes(CombineHashCodes(h1, h2, h3, h4), h5);
         }
-
-        internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5, int h6) {
-            return CombineHashCodes(CombineHashCodes(h1, h2, h3, h4), CombineHashCodes(h5, h6));
+        
+        internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5, int h6)
+        {
+            return CombineHashCodes(CombineHashCodes(h1, h2, h3, h4, h5), h6);
         }
 
-        internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5, int h6, int h7) {
-            return CombineHashCodes(CombineHashCodes(h1, h2, h3, h4), CombineHashCodes(h5, h6, h7));
+        internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5, int h6, int h7)
+        {
+            return CombineHashCodes(CombineHashCodes(h1, h2, h3, h4, h5, h6), h7);
         }
 
-        internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5, int h6, int h7, int h8) {
-            return CombineHashCodes(CombineHashCodes(h1, h2, h3, h4), CombineHashCodes(h5, h6, h7, h8));
+        internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5, int h6, int h7, int h8)
+        {
+            return CombineHashCodes(CombineHashCodes(h1, h2, h3, h4, h5, h6, h7), h8);
         }
     }
 
@@ -93,8 +107,16 @@ namespace System {
             m_Item1 = item1;
         }
 
-        public override Boolean Equals(Object obj) {
-            return ((IStructuralEquatable) this).Equals(obj, EqualityComparer<Object>.Default);
+        public override bool Equals(object obj)
+        {
+            var other = obj as Tuple<T1>;
+            
+            if (other == null)
+            {
+                return false;
+            }
+            
+            return EqualityComparer<T1>.Default.Equals(Item1, other.Item1);
         }
 
         Boolean IStructuralEquatable.Equals(Object other, IEqualityComparer comparer) {
@@ -125,8 +147,9 @@ namespace System {
             return comparer.Compare(m_Item1, objTuple.m_Item1);
         }
 
-        public override int GetHashCode() {
-            return ((IStructuralEquatable) this).GetHashCode(EqualityComparer<Object>.Default);
+        public override int GetHashCode()
+        {
+            return EqualityComparer<T1>.Default.GetHashCode(Item1);
         }
 
         Int32 IStructuralEquatable.GetHashCode(IEqualityComparer comparer) {
@@ -169,8 +192,17 @@ namespace System {
             m_Item2 = item2;
         }
 
-        public override Boolean Equals(Object obj) {
-            return ((IStructuralEquatable) this).Equals(obj, EqualityComparer<Object>.Default);;
+        public override bool Equals(object obj)
+        {
+            var other = obj as Tuple<T1, T2>;
+            
+            if (other == null)
+            {
+                return false;
+            }
+            
+            return EqualityComparer<T1>.Default.Equals(Item1, other.Item1)
+                && EqualityComparer<T2>.Default.Equals(Item2, other.Item2);
         }
 
         Boolean IStructuralEquatable.Equals(Object other, IEqualityComparer comparer) {
@@ -207,8 +239,10 @@ namespace System {
             return comparer.Compare(m_Item2, objTuple.m_Item2);
         }
 
-        public override int GetHashCode() {
-            return ((IStructuralEquatable) this).GetHashCode(EqualityComparer<Object>.Default);
+        public override int GetHashCode()
+        {
+            return Tuple.CombineHashCodes(EqualityComparer<T1>.Default.GetHashCode(Item1),
+                                          EqualityComparer<T2>.Default.GetHashCode(Item2));
         }
 
         Int32 IStructuralEquatable.GetHashCode(IEqualityComparer comparer) {
@@ -256,8 +290,18 @@ namespace System {
             m_Item3 = item3;
         }
 
-        public override Boolean Equals(Object obj) {
-            return ((IStructuralEquatable) this).Equals(obj, EqualityComparer<Object>.Default);;
+        public override bool Equals(object obj)
+        {
+            var other = obj as Tuple<T1, T2, T3>;
+            
+            if (other == null)
+            {
+                return false;
+            }
+            
+            return EqualityComparer<T1>.Default.Equals(Item1, other.Item1)
+                && EqualityComparer<T2>.Default.Equals(Item2, other.Item2)
+                && EqualityComparer<T3>.Default.Equals(Item3, other.Item3);
         }
 
         Boolean IStructuralEquatable.Equals(Object other, IEqualityComparer comparer) {
@@ -298,8 +342,11 @@ namespace System {
             return comparer.Compare(m_Item3, objTuple.m_Item3);
         }
 
-        public override int GetHashCode() {
-            return ((IStructuralEquatable) this).GetHashCode(EqualityComparer<Object>.Default);
+        public override int GetHashCode()
+        {
+            return Tuple.CombineHashCodes(EqualityComparer<T1>.Default.GetHashCode(Item1),
+                                          EqualityComparer<T2>.Default.GetHashCode(Item2),
+                                          EqualityComparer<T3>.Default.GetHashCode(Item3));
         }
 
         Int32 IStructuralEquatable.GetHashCode(IEqualityComparer comparer) {
@@ -352,8 +399,19 @@ namespace System {
             m_Item4 = item4;
         }
 
-        public override Boolean Equals(Object obj) {
-            return ((IStructuralEquatable) this).Equals(obj, EqualityComparer<Object>.Default);;
+        public override bool Equals(object obj)
+        {
+            var other = obj as Tuple<T1, T2, T3, T4>;
+            
+            if (other == null)
+            {
+                return false;
+            }
+            
+            return EqualityComparer<T1>.Default.Equals(Item1, other.Item1)
+                && EqualityComparer<T2>.Default.Equals(Item2, other.Item2)
+                && EqualityComparer<T3>.Default.Equals(Item3, other.Item3)
+                && EqualityComparer<T4>.Default.Equals(Item4, other.Item4);
         }
 
         Boolean IStructuralEquatable.Equals(Object other, IEqualityComparer comparer) {
@@ -398,8 +456,12 @@ namespace System {
             return comparer.Compare(m_Item4, objTuple.m_Item4);
         }
 
-        public override int GetHashCode() {
-            return ((IStructuralEquatable) this).GetHashCode(EqualityComparer<Object>.Default);
+        public override int GetHashCode()
+        {
+            return Tuple.CombineHashCodes(EqualityComparer<T1>.Default.GetHashCode(Item1),
+                                          EqualityComparer<T2>.Default.GetHashCode(Item2),
+                                          EqualityComparer<T3>.Default.GetHashCode(Item3),
+                                          EqualityComparer<T4>.Default.GetHashCode(Item4));
         }
 
         Int32 IStructuralEquatable.GetHashCode(IEqualityComparer comparer) {
@@ -457,8 +519,20 @@ namespace System {
             m_Item5 = item5;
         }
 
-        public override Boolean Equals(Object obj) {
-            return ((IStructuralEquatable) this).Equals(obj, EqualityComparer<Object>.Default);;
+        public override bool Equals(object obj)
+        {
+            var other = obj as Tuple<T1, T2, T3, T4, T5>;
+            
+            if (other == null)
+            {
+                return false;
+            }
+            
+            return EqualityComparer<T1>.Default.Equals(Item1, other.Item1)
+                && EqualityComparer<T2>.Default.Equals(Item2, other.Item2)
+                && EqualityComparer<T3>.Default.Equals(Item3, other.Item3)
+                && EqualityComparer<T4>.Default.Equals(Item4, other.Item4)
+                && EqualityComparer<T5>.Default.Equals(Item5, other.Item5);
         }
 
         Boolean IStructuralEquatable.Equals(Object other, IEqualityComparer comparer) {
@@ -507,8 +581,13 @@ namespace System {
             return comparer.Compare(m_Item5, objTuple.m_Item5);
         }
 
-        public override int GetHashCode() {
-            return ((IStructuralEquatable) this).GetHashCode(EqualityComparer<Object>.Default);
+        public override int GetHashCode()
+        {
+            return Tuple.CombineHashCodes(EqualityComparer<T1>.Default.GetHashCode(Item1),
+                                          EqualityComparer<T2>.Default.GetHashCode(Item2),
+                                          EqualityComparer<T3>.Default.GetHashCode(Item3),
+                                          EqualityComparer<T4>.Default.GetHashCode(Item4),
+                                          EqualityComparer<T5>.Default.GetHashCode(Item5));
         }
 
         Int32 IStructuralEquatable.GetHashCode(IEqualityComparer comparer) {
@@ -571,8 +650,21 @@ namespace System {
             m_Item6 = item6;
         }
 
-        public override Boolean Equals(Object obj) {
-            return ((IStructuralEquatable) this).Equals(obj, EqualityComparer<Object>.Default);;
+        public override bool Equals(object obj)
+        {
+            var other = obj as Tuple<T1, T2, T3, T4, T5, T6>;
+            
+            if (other == null)
+            {
+                return false;
+            }
+            
+            return EqualityComparer<T1>.Default.Equals(Item1, other.Item1)
+                && EqualityComparer<T2>.Default.Equals(Item2, other.Item2)
+                && EqualityComparer<T3>.Default.Equals(Item3, other.Item3)
+                && EqualityComparer<T4>.Default.Equals(Item4, other.Item4)
+                && EqualityComparer<T5>.Default.Equals(Item5, other.Item5)
+                && EqualityComparer<T6>.Default.Equals(Item6, other.Item6);
         }
 
         Boolean IStructuralEquatable.Equals(Object other, IEqualityComparer comparer) {
@@ -625,8 +717,14 @@ namespace System {
             return comparer.Compare(m_Item6, objTuple.m_Item6);
         }
 
-        public override int GetHashCode() {
-            return ((IStructuralEquatable) this).GetHashCode(EqualityComparer<Object>.Default);
+        public override int GetHashCode()
+        {
+            return Tuple.CombineHashCodes(EqualityComparer<T1>.Default.GetHashCode(Item1),
+                                          EqualityComparer<T2>.Default.GetHashCode(Item2),
+                                          EqualityComparer<T3>.Default.GetHashCode(Item3),
+                                          EqualityComparer<T4>.Default.GetHashCode(Item4),
+                                          EqualityComparer<T5>.Default.GetHashCode(Item5),
+                                          EqualityComparer<T6>.Default.GetHashCode(Item6));
         }
 
         Int32 IStructuralEquatable.GetHashCode(IEqualityComparer comparer) {
@@ -693,9 +791,23 @@ namespace System {
             m_Item6 = item6;
             m_Item7 = item7;
         }
-
-        public override Boolean Equals(Object obj) {
-            return ((IStructuralEquatable) this).Equals(obj, EqualityComparer<Object>.Default);;
+        
+        public override bool Equals(object obj)
+        {
+            var other = obj as Tuple<T1, T2, T3, T4, T5, T6, T7>;
+            
+            if (other == null)
+            {
+                return false;
+            }
+            
+            return EqualityComparer<T1>.Default.Equals(Item1, other.Item1)
+                && EqualityComparer<T2>.Default.Equals(Item2, other.Item2)
+                && EqualityComparer<T3>.Default.Equals(Item3, other.Item3)
+                && EqualityComparer<T4>.Default.Equals(Item4, other.Item4)
+                && EqualityComparer<T5>.Default.Equals(Item5, other.Item5)
+                && EqualityComparer<T6>.Default.Equals(Item6, other.Item6)
+                && EqualityComparer<T7>.Default.Equals(Item7, other.Item7);
         }
 
         Boolean IStructuralEquatable.Equals(Object other, IEqualityComparer comparer) {
@@ -752,8 +864,15 @@ namespace System {
             return comparer.Compare(m_Item7, objTuple.m_Item7);
         }
 
-        public override int GetHashCode() {
-            return ((IStructuralEquatable) this).GetHashCode(EqualityComparer<Object>.Default);
+        public override int GetHashCode()
+        {
+            return Tuple.CombineHashCodes(EqualityComparer<T1>.Default.GetHashCode(Item1),
+                                          EqualityComparer<T2>.Default.GetHashCode(Item2),
+                                          EqualityComparer<T3>.Default.GetHashCode(Item3),
+                                          EqualityComparer<T4>.Default.GetHashCode(Item4),
+                                          EqualityComparer<T5>.Default.GetHashCode(Item5),
+                                          EqualityComparer<T6>.Default.GetHashCode(Item6),
+                                          EqualityComparer<T7>.Default.GetHashCode(Item7));
         }
 
         Int32 IStructuralEquatable.GetHashCode(IEqualityComparer comparer) {
@@ -830,8 +949,26 @@ namespace System {
             m_Rest = rest;
         }
 
-        public override Boolean Equals(Object obj) {
-            return ((IStructuralEquatable) this).Equals(obj, EqualityComparer<Object>.Default);;
+        public override bool Equals(object obj)
+        {
+            var other = obj as Tuple<T1, T2, T3, T4, T5, T6, T7, TRest>;
+            
+            if (other == null)
+            {
+                return false;
+            }
+            
+            return EqualityComparer<T1>.Default.Equals(Item1, other.Item1)
+                && EqualityComparer<T2>.Default.Equals(Item2, other.Item2)
+                && EqualityComparer<T3>.Default.Equals(Item3, other.Item3)
+                && EqualityComparer<T4>.Default.Equals(Item4, other.Item4)
+                && EqualityComparer<T5>.Default.Equals(Item5, other.Item5)
+                && EqualityComparer<T6>.Default.Equals(Item6, other.Item6)
+                && EqualityComparer<T7>.Default.Equals(Item7, other.Item7)
+                && EqualityComparer<TRest>.Default.Equals(Rest, other.Rest); // object.Equals(Rest, other.Rest) is not used here, since this
+                                                                             // may be faster if 1) Tuple eventually implements IEquatable or
+                                                                             // 2) calls to EqualityComparer.Default.Equals are intrinsified
+                                                                             // by the JIT.
         }
 
         Boolean IStructuralEquatable.Equals(Object other, IEqualityComparer comparer) {
@@ -892,17 +1029,78 @@ namespace System {
             return comparer.Compare(m_Rest, objTuple.m_Rest);
         }
 
-        public override int GetHashCode() {
-            return ((IStructuralEquatable) this).GetHashCode(EqualityComparer<Object>.Default);
+        public override int GetHashCode()
+        {
+            // We want to have a limited hash in this case. We'll use the last 8 elements of the tuple
+
+            var rest = (ITuple)Rest; // We checked that Rest was an ITuple in the constructor
+            int size = rest.Size;
+            
+            if (size >= 8)
+            {
+                return rest.GetHashCode();
+            }
+            
+            // In this case, the Rest member has less than 8 elements so we need to combine some of our elements with the ones in Rest
+            int before = 8 - size; // Number of elements we will hash in this tuple before Rest
+            switch (before)
+            {
+                case 1:
+                    return Tuple.CombineHashCodes(EqualityComparer<T7>.Default.GetHashCode(Item7),
+                                                  rest.GetHashCode());
+                case 2:
+                    return Tuple.CombineHashCodes(EqualityComparer<T6>.Default.GetHashCode(Item6),
+                                                  EqualityComparer<T7>.Default.GetHashCode(Item7),
+                                                  rest.GetHashCode());
+                case 3:
+                    return Tuple.CombineHashCodes(EqualityComparer<T5>.Default.GetHashCode(Item5),
+                                                  EqualityComparer<T6>.Default.GetHashCode(Item6),
+                                                  EqualityComparer<T7>.Default.GetHashCode(Item7),
+                                                  rest.GetHashCode());
+                case 4:
+                    return Tuple.CombineHashCodes(EqualityComparer<T4>.Default.GetHashCode(Item4),
+                                                  EqualityComparer<T5>.Default.GetHashCode(Item5),
+                                                  EqualityComparer<T6>.Default.GetHashCode(Item6),
+                                                  EqualityComparer<T7>.Default.GetHashCode(Item7),
+                                                  rest.GetHashCode());
+                case 5:
+                    return Tuple.CombineHashCodes(EqualityComparer<T3>.Default.GetHashCode(Item3),
+                                                  EqualityComparer<T4>.Default.GetHashCode(Item4),
+                                                  EqualityComparer<T5>.Default.GetHashCode(Item5),
+                                                  EqualityComparer<T6>.Default.GetHashCode(Item6),
+                                                  EqualityComparer<T7>.Default.GetHashCode(Item7),
+                                                  rest.GetHashCode());
+                case 6:
+                    return Tuple.CombineHashCodes(EqualityComparer<T2>.Default.GetHashCode(Item2),
+                                                  EqualityComparer<T3>.Default.GetHashCode(Item3),
+                                                  EqualityComparer<T4>.Default.GetHashCode(Item4),
+                                                  EqualityComparer<T5>.Default.GetHashCode(Item5),
+                                                  EqualityComparer<T6>.Default.GetHashCode(Item6),
+                                                  EqualityComparer<T7>.Default.GetHashCode(Item7),
+                                                  rest.GetHashCode());
+                case 7:
+                    return Tuple.CombineHashCodes(EqualityComparer<T1>.Default.GetHashCode(Item1),
+                                                  EqualityComparer<T2>.Default.GetHashCode(Item2),
+                                                  EqualityComparer<T3>.Default.GetHashCode(Item3),
+                                                  EqualityComparer<T4>.Default.GetHashCode(Item4),
+                                                  EqualityComparer<T5>.Default.GetHashCode(Item5),
+                                                  EqualityComparer<T6>.Default.GetHashCode(Item6),
+                                                  EqualityComparer<T7>.Default.GetHashCode(Item7),
+                                                  rest.GetHashCode());
+            }
+            
+            Contract.Assert(false, "Missed all cases for computing Tuple hash code");
+            return -1;
         }
 
         Int32 IStructuralEquatable.GetHashCode(IEqualityComparer comparer) {
             // We want to have a limited hash in this case.  We'll use the last 8 elements of the tuple
             ITuple t = (ITuple) m_Rest;
-            if(t.Size >= 8) { return t.GetHashCode(comparer); }
+            int size = t.Size; // cache the size to avoid an unncessary interface call
+            if (size >= 8) { return t.GetHashCode(comparer); }
             
             // In this case, the rest memeber has less than 8 elements so we need to combine some our elements with the elements in rest
-            int k = 8 - t.Size;
+            int k = 8 - size;
             switch(k) {
                 case 1:
                 return Tuple.CombineHashCodes(comparer.GetHashCode(m_Item7), t.GetHashCode(comparer));