Perf improvements to Span and Memory (dotnet/coreclr#20771)
authorLevi Broderick <GrabYourPitchforks@users.noreply.github.com>
Sat, 3 Nov 2018 02:15:43 +0000 (19:15 -0700)
committerAhson Khan <ahkha@microsoft.com>
Sat, 3 Nov 2018 02:15:43 +0000 (19:15 -0700)
* Perf improvements to Span and Memory
- Improves perf of AsSpan, AsMemory, ctor, and Slice

* PR feedback - add comments

Commit migrated from https://github.com/dotnet/coreclr/commit/e8438d33b28e32fa7eb879aa70181087b277f29a

src/libraries/System.Private.CoreLib/src/System/Memory.cs
src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Fast.cs
src/libraries/System.Private.CoreLib/src/System/ReadOnlyMemory.cs
src/libraries/System.Private.CoreLib/src/System/ReadOnlySpan.Fast.cs
src/libraries/System.Private.CoreLib/src/System/Span.Fast.cs

index 1a75567..e4596d3 100644 (file)
@@ -104,8 +104,14 @@ namespace System
             }
             if (default(T) == null && array.GetType() != typeof(T[]))
                 ThrowHelper.ThrowArrayTypeMismatchException();
+#if BIT64
+            // See comment in Span<T>.Slice for how this works.
+            if ((ulong)(uint)start + (ulong)(uint)length > (ulong)(uint)array.Length)
+                ThrowHelper.ThrowArgumentOutOfRangeException();
+#else
             if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start))
                 ThrowHelper.ThrowArgumentOutOfRangeException();
+#endif
 
             _object = array;
             _index = start;
@@ -250,10 +256,14 @@ namespace System
             // Used to maintain the high-bit which indicates whether the Memory has been pre-pinned or not.
             int capturedLength = _length;
             int actualLength = capturedLength & RemoveFlagsBitMask;
+#if BIT64
+            // See comment in Span<T>.Slice for how this works.
+            if ((ulong)(uint)start + (ulong)(uint)length > (ulong)(uint)actualLength)
+                ThrowHelper.ThrowArgumentOutOfRangeException();
+#else
             if ((uint)start > (uint)actualLength || (uint)length > (uint)(actualLength - start))
-            {
                 ThrowHelper.ThrowArgumentOutOfRangeException();
-            }
+#endif
 
             // Set the high-bit to match the this._length high bit (1 for pre-pinned, 0 for unpinned).
             return new Memory<T>(_object, _index + start, length | (capturedLength & ~RemoveFlagsBitMask));
index 31051c8..99fb83e 100644 (file)
@@ -450,8 +450,14 @@ namespace System
                 return default;
             }
 
+#if BIT64
+            // See comment in Span<T>.Slice for how this works.
+            if ((ulong)(uint)start + (ulong)(uint)length > (ulong)(uint)text.Length)
+                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+#else
             if ((uint)start > (uint)text.Length || (uint)length > (uint)(text.Length - start))
                 ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+#endif
 
             return new ReadOnlySpan<char>(ref Unsafe.Add(ref text.GetRawStringData(), start), length);
         }
@@ -506,8 +512,14 @@ namespace System
                 return default;
             }
 
+#if BIT64
+            // See comment in Span<T>.Slice for how this works.
+            if ((ulong)(uint)start + (ulong)(uint)length > (ulong)(uint)text.Length)
+                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+#else
             if ((uint)start > (uint)text.Length || (uint)length > (uint)(text.Length - start))
                 ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+#endif
 
             return new ReadOnlyMemory<char>(text, start, length);
         }
index 02445ec..7a332c1 100644 (file)
@@ -77,8 +77,14 @@ namespace System
                 this = default;
                 return; // returns default
             }
+#if BIT64
+            // See comment in Span<T>.Slice for how this works.
+            if ((ulong)(uint)start + (ulong)(uint)length > (ulong)(uint)array.Length)
+                ThrowHelper.ThrowArgumentOutOfRangeException();
+#else
             if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start))
                 ThrowHelper.ThrowArgumentOutOfRangeException();
+#endif
 
             _object = array;
             _index = start;
@@ -172,10 +178,14 @@ namespace System
             // Used to maintain the high-bit which indicates whether the Memory has been pre-pinned or not.
             int capturedLength = _length;
             int actualLength = _length & RemoveFlagsBitMask;
+#if BIT64
+            // See comment in Span<T>.Slice for how this works.
+            if ((ulong)(uint)start + (ulong)(uint)length > (ulong)(uint)actualLength)
+                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+#else
             if ((uint)start > (uint)actualLength || (uint)length > (uint)(actualLength - start))
-            {
                 ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
-            }
+#endif
 
             // Set the high-bit to match the this._length high bit (1 for pre-pinned, 0 for unpinned).
             return new ReadOnlyMemory<T>(_object, _index + start, length | (capturedLength & ~RemoveFlagsBitMask));
index 8ba8fd6..3077e2f 100644 (file)
@@ -75,8 +75,14 @@ namespace System
                 this = default;
                 return; // returns default
             }
+#if BIT64
+            // See comment in Span<T>.Slice for how this works.
+            if ((ulong)(uint)start + (ulong)(uint)length > (ulong)(uint)array.Length)
+                ThrowHelper.ThrowArgumentOutOfRangeException();
+#else
             if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start))
                 ThrowHelper.ThrowArgumentOutOfRangeException();
+#endif
 
             _pointer = new ByReference<T>(ref Unsafe.Add(ref Unsafe.As<byte, T>(ref array.GetRawSzArrayData()), start));
             _length = length;
@@ -259,8 +265,14 @@ namespace System
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public ReadOnlySpan<T> Slice(int start, int length)
         {
+#if BIT64
+            // See comment in Span<T>.Slice for how this works.
+            if ((ulong)(uint)start + (ulong)(uint)length > (ulong)(uint)_length)
+                ThrowHelper.ThrowArgumentOutOfRangeException();
+#else
             if ((uint)start > (uint)_length || (uint)length > (uint)(_length - start))
                 ThrowHelper.ThrowArgumentOutOfRangeException();
+#endif
 
             return new ReadOnlySpan<T>(ref Unsafe.Add(ref _pointer.Value, start), length);
         }
index 3073592..626c06e 100644 (file)
@@ -81,8 +81,14 @@ namespace System
             }
             if (default(T) == null && array.GetType() != typeof(T[]))
                 ThrowHelper.ThrowArrayTypeMismatchException();
+#if BIT64
+            // See comment in Span<T>.Slice for how this works.
+            if ((ulong)(uint)start + (ulong)(uint)length > (ulong)(uint)array.Length)
+                ThrowHelper.ThrowArgumentOutOfRangeException();
+#else
             if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start))
                 ThrowHelper.ThrowArgumentOutOfRangeException();
+#endif
 
             _pointer = new ByReference<T>(ref Unsafe.Add(ref Unsafe.As<byte, T>(ref array.GetRawSzArrayData()), start));
             _length = length;
@@ -338,8 +344,19 @@ namespace System
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public Span<T> Slice(int start, int length)
         {
+#if BIT64
+            // Since start and length are both 32-bit, their sum can be computed across a 64-bit domain
+            // without loss of fidelity. The cast to uint before the cast to ulong ensures that the
+            // extension from 32- to 64-bit is zero-extending rather than sign-extending. The end result
+            // of this is that if either input is negative or if the input sum overflows past Int32.MaxValue,
+            // that information is captured correctly in the comparison against the backing _length field.
+            // We don't use this same mechanism in a 32-bit process due to the overhead of 64-bit arithmetic.
+            if ((ulong)(uint)start + (ulong)(uint)length > (ulong)(uint)_length)
+                ThrowHelper.ThrowArgumentOutOfRangeException();
+#else
             if ((uint)start > (uint)_length || (uint)length > (uint)(_length - start))
                 ThrowHelper.ThrowArgumentOutOfRangeException();
+#endif
 
             return new Span<T>(ref Unsafe.Add(ref _pointer.Value, start), length);
         }