Optimize MemoryExtensions SequenceEqual for more T's (dotnet/corefx#27859)
authorAtsushi Kanamori <AtsushiKan@users.noreply.github.com>
Mon, 12 Mar 2018 16:57:10 +0000 (09:57 -0700)
committerJan Kotas <jkotas@microsoft.com>
Mon, 12 Mar 2018 18:20:07 +0000 (11:20 -0700)
* Optimize MemoryExtensions SequenceEqual for more T's

https://github.com/dotnet/corefx/issues/27487

* Add testcoverage for SequenceEqual<long>

* Remove #if BIT64 dependency for netstandard

Signed-off-by: dotnet-bot-corefx-mirror <dotnet-bot@microsoft.com>
src/mscorlib/shared/System/MemoryExtensions.cs
src/mscorlib/shared/System/SpanHelpers.Byte.cs

index 0f8f167..4f278b6 100644 (file)
@@ -10,6 +10,16 @@ using System.Runtime.InteropServices;
 using Internal.Runtime.CompilerServices;
 #endif
 
+#if netstandard
+using nuint=System.NUInt;
+#else
+#if BIT64
+using nuint=System.UInt64;
+#else
+using nuint=System.UInt32;
+#endif // BIT64
+#endif // netstandard
+
 namespace System
 {
     /// <summary>
@@ -245,12 +255,34 @@ namespace System
             where T : IEquatable<T>
         {
             int length = first.Length;
-            if (typeof(T) == typeof(byte))
+            if (typeof(T) == typeof(byte) || typeof(T) == typeof(sbyte))
                 return length == second.Length &&
                 SpanHelpers.SequenceEqual(
                     ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(first)),
                     ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(second)),
                     length);
+
+            if (typeof(T) == typeof(char) || typeof(T) == typeof(short) || typeof(T) == typeof(ushort))
+                return length == second.Length &&
+                SpanHelpers.SequenceEqualBytes(
+                    ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(first)),
+                    ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(second)),
+                    ((nuint)length) * 2); // If this multiplication overflows, the Span we got overflows the entire address range. There's no happy outcome for this api in such a case so we choose not to take the overhead of checking. 
+
+            if (typeof(T) == typeof(int) || typeof(T) == typeof(uint))
+                return length == second.Length &&
+                SpanHelpers.SequenceEqualBytes(
+                    ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(first)),
+                    ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(second)),
+                    ((nuint)length) * 4); // If this multiplication overflows, the Span we got overflows the entire address range. There's no happy outcome for this api in such a case so we choose not to take the overhead of checking. 
+
+            if (typeof(T) == typeof(long) || typeof(T) == typeof(ulong))
+                return length == second.Length &&
+                SpanHelpers.SequenceEqualBytes(
+                    ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(first)),
+                    ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(second)),
+                    ((nuint)length) * 8); // If this multiplication overflows, the Span we got overflows the entire address range. There's no happy outcome for this api in such a case so we choose not to take the overhead of checking. 
+
             return length == second.Length && SpanHelpers.SequenceEqual(ref MemoryMarshal.GetReference(first), ref MemoryMarshal.GetReference(second), length);
         }
 
index 860b2ef..3e699dd 100644 (file)
@@ -13,6 +13,16 @@ using Internal.Runtime.CompilerServices;
 using System.Numerics;
 #endif
 
+#if netstandard
+using nuint=System.NUInt;
+#else
+#if BIT64
+using nuint=System.UInt64;
+#else
+using nuint=System.UInt32;
+#endif // BIT64
+#endif // netstandard
+
 namespace System
 {
     internal static partial class SpanHelpers
@@ -886,10 +896,14 @@ namespace System
             return (int)(byte*)(index + 7);
         }
 
-        public static unsafe bool SequenceEqual(ref byte first, ref byte second, int length)
-        {
-            Debug.Assert(length >= 0);
+        // This overload exists to maintain the "pit of success" where apis that specializes for T being byte continue to get directed to the fast byte-based version via the C#
+        // resolution rules.
+        public static bool SequenceEqual(ref byte first, ref byte second, int length) => SequenceEqualBytes(ref first, ref second, (nuint)length);
 
+        // Optimized byte-based SequenceEquals. The "length" parameter for this one is declared a nuint rather than int as we also use it for types other than byte
+        // where the length can exceed 2Gb once scaled by sizeof(T).
+        public static unsafe bool SequenceEqualBytes(ref byte first, ref byte second, nuint length)
+        {
             if (Unsafe.AreSame(ref first, ref second))
                 goto Equal;