From d2f0bf7898f057ec6dc9f8c06c216cf9ed81e7e4 Mon Sep 17 00:00:00 2001
From: Sergio Pedri <sergio0694@live.com>
Date: Mon, 3 Aug 2020 23:38:52 +0200
Subject: [PATCH 1/8] Reduced memory usage in Count tests

---
 .../Test_ReadOnlySpanExtensions.Count.cs      | 76 ++++++++++++++++---
 .../Extensions/Test_ReadOnlySpanExtensions.cs | 12 ++-
 2 files changed, 74 insertions(+), 14 deletions(-)

diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.Count.cs b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.Count.cs
index 3966aac..a3fd7fd 100644
--- a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.Count.cs
+++ b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.Count.cs
@@ -3,12 +3,16 @@
 // See the LICENSE file in the project root for more information.
 
 using System;
+using System.Buffers;
 using System.Diagnostics.CodeAnalysis;
 using System.Diagnostics.Contracts;
+using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
 using Microsoft.Toolkit.HighPerformance.Extensions;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 
+#nullable enable
+
 namespace UnitTests.HighPerformance.Extensions
 {
     public partial class Test_ReadOnlySpanExtensions
@@ -168,15 +172,15 @@ public void Test_ReadOnlySpanExtensions_FilledCount64()
         /// <typeparam name="T">The type to test.</typeparam>
         /// <param name="value">The target value to look for.</param>
         /// <param name="provider">The function to use to create random data.</param>
-        private static void TestForType<T>(T value, Func<int, T, T[]> provider)
+        private static void TestForType<T>(T value, Func<int, T, UnmanagedSpanOwner<T>> provider)
             where T : unmanaged, IEquatable<T>
         {
             foreach (var count in TestCounts)
             {
-                T[] data = provider(count, value);
+                using UnmanagedSpanOwner<T> data = provider(count, value);
 
-                int result = data.Count(value);
-                int expected = CountWithForeach(data, value);
+                int result = data.Span.Count(value);
+                int expected = CountWithForeach(data.Span, value);
 
                 Assert.AreEqual(result, expected, $"Failed {typeof(T)} test with count {count}: got {result} instead of {expected}");
             }
@@ -214,14 +218,14 @@ private static int CountWithForeach<T>(ReadOnlySpan<T> span, T value)
         /// <param name="value">The value to look for.</param>
         /// <returns>An array of random <typeparamref name="T"/> elements.</returns>
         [Pure]
-        private static T[] CreateRandomData<T>(int count, T value)
+        private static UnmanagedSpanOwner<T> CreateRandomData<T>(int count, T value)
             where T : unmanaged
         {
             var random = new Random(count);
 
-            T[] data = new T[count];
+            UnmanagedSpanOwner<T> data = new UnmanagedSpanOwner<T>(count);
 
-            foreach (ref byte n in MemoryMarshal.AsBytes(data.AsSpan()))
+            foreach (ref byte n in MemoryMarshal.AsBytes(data.Span))
             {
                 n = (byte)random.Next(0, byte.MaxValue);
             }
@@ -229,9 +233,11 @@ private static T[] CreateRandomData<T>(int count, T value)
             // Fill at least 20% of the items with a matching value
             int minimum = count / 20;
 
+            Span<T> span = data.Span;
+
             for (int i = 0; i < minimum; i++)
             {
-                data[random.Next(0, count)] = value;
+                span[random.Next(0, count)] = value;
             }
 
             return data;
@@ -245,14 +251,62 @@ private static T[] CreateRandomData<T>(int count, T value)
         /// <param name="value">The value to use to populate the array.</param>
         /// <returns>An array of <typeparamref name="T"/> elements.</returns>
         [Pure]
-        private static T[] CreateFilledData<T>(int count, T value)
+        private static UnmanagedSpanOwner<T> CreateFilledData<T>(int count, T value)
             where T : unmanaged
         {
-            T[] data = new T[count];
+            UnmanagedSpanOwner<T> data = new UnmanagedSpanOwner<T>(count);
 
-            data.AsSpan().Fill(value);
+            data.Span.Fill(value);
 
             return data;
         }
+
+        /// <summary>
+        /// An owner for a buffer of an unmanaged type, recycling <see cref="byte"/> arrays to save memory.
+        /// </summary>
+        /// <typeparam name="T">The type of items to store in the rented buffers.</typeparam>
+        private sealed class UnmanagedSpanOwner<T> : IDisposable
+            where T : unmanaged
+        {
+            /// <summary>
+            /// The size of the current instance
+            /// </summary>
+            private readonly int length;
+
+            /// <summary>
+            /// The underlying <see cref="byte"/> array.
+            /// </summary>
+            private byte[]? array;
+
+            /// <summary>
+            /// Initializes a new instance of the <see cref="UnmanagedSpanOwner{T}"/> class.
+            /// </summary>
+            /// <param name="size">The size of the buffer to rent.</param>
+            public UnmanagedSpanOwner(int size)
+            {
+                this.array = ArrayPool<byte>.Shared.Rent(size * Unsafe.SizeOf<T>());
+                this.length = size;
+            }
+
+            /// <inheritdoc/>
+            public void Dispose()
+            {
+                byte[]? array = this.array;
+
+                if (array is null)
+                {
+                    return;
+                }
+
+                this.array = null;
+
+                ArrayPool<byte>.Shared.Return(array);
+            }
+
+            /// <summary>
+            /// Gets the <see cref="Memory{T}"/> for the current instance.
+            /// </summary>
+            public Span<T> Span => this.array.AsSpan().Cast<byte, T>().Slice(0, this.length);
+        }
     }
 }
diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.cs b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.cs
index d3d2010..c431e49 100644
--- a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.cs
+++ b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.cs
@@ -18,7 +18,9 @@ public partial class Test_ReadOnlySpanExtensions
         [TestMethod]
         public void Test_ReadOnlySpanExtensions_DangerousGetReference()
         {
-            ReadOnlySpan<int> data = CreateRandomData<int>(12, default).AsSpan();
+            using var owner = CreateRandomData<int>(12, default);
+
+            ReadOnlySpan<int> data = owner.Span;
 
             ref int r0 = ref data.DangerousGetReference();
             ref int r1 = ref Unsafe.AsRef(data[0]);
@@ -30,7 +32,9 @@ public void Test_ReadOnlySpanExtensions_DangerousGetReference()
         [TestMethod]
         public void Test_ReadOnlySpanExtensions_DangerousGetReferenceAt_Zero()
         {
-            ReadOnlySpan<int> data = CreateRandomData<int>(12, default).AsSpan();
+            using var owner = CreateRandomData<int>(12, default);
+
+            ReadOnlySpan<int> data = owner.Span;
 
             ref int r0 = ref data.DangerousGetReference();
             ref int r1 = ref data.DangerousGetReferenceAt(0);
@@ -42,7 +46,9 @@ public void Test_ReadOnlySpanExtensions_DangerousGetReferenceAt_Zero()
         [TestMethod]
         public void Test_ReadOnlySpanExtensions_DangerousGetReferenceAt_Index()
         {
-            ReadOnlySpan<int> data = CreateRandomData<int>(12, default).AsSpan();
+            using var owner = CreateRandomData<int>(12, default);
+
+            ReadOnlySpan<int> data = owner.Span;
 
             ref int r0 = ref data.DangerousGetReferenceAt(5);
             ref int r1 = ref Unsafe.AsRef(data[5]);

From 917b96acbbc439fa5b58eb2d72ac5ddf3f3c18d1 Mon Sep 17 00:00:00 2001
From: Sergio Pedri <sergio0694@live.com>
Date: Tue, 4 Aug 2020 01:59:32 +0200
Subject: [PATCH 2/8] Attempt to fix CI with manual memory management

---
 .../Test_ReadOnlySpanExtensions.Count.cs      | 19 +++++++++----------
 1 file changed, 9 insertions(+), 10 deletions(-)

diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.Count.cs b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.Count.cs
index a3fd7fd..4be89dd 100644
--- a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.Count.cs
+++ b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.Count.cs
@@ -3,7 +3,6 @@
 // See the LICENSE file in the project root for more information.
 
 using System;
-using System.Buffers;
 using System.Diagnostics.CodeAnalysis;
 using System.Diagnostics.Contracts;
 using System.Runtime.CompilerServices;
@@ -265,7 +264,7 @@ private static UnmanagedSpanOwner<T> CreateFilledData<T>(int count, T value)
         /// An owner for a buffer of an unmanaged type, recycling <see cref="byte"/> arrays to save memory.
         /// </summary>
         /// <typeparam name="T">The type of items to store in the rented buffers.</typeparam>
-        private sealed class UnmanagedSpanOwner<T> : IDisposable
+        private sealed unsafe class UnmanagedSpanOwner<T> : IDisposable
             where T : unmanaged
         {
             /// <summary>
@@ -274,9 +273,9 @@ private sealed class UnmanagedSpanOwner<T> : IDisposable
             private readonly int length;
 
             /// <summary>
-            /// The underlying <see cref="byte"/> array.
+            /// The pointer to the underlying <see cref="byte"/> array.
             /// </summary>
-            private byte[]? array;
+            private IntPtr ptr;
 
             /// <summary>
             /// Initializes a new instance of the <see cref="UnmanagedSpanOwner{T}"/> class.
@@ -284,29 +283,29 @@ private sealed class UnmanagedSpanOwner<T> : IDisposable
             /// <param name="size">The size of the buffer to rent.</param>
             public UnmanagedSpanOwner(int size)
             {
-                this.array = ArrayPool<byte>.Shared.Rent(size * Unsafe.SizeOf<T>());
+                this.ptr = Marshal.AllocHGlobal(size * Unsafe.SizeOf<T>());
                 this.length = size;
             }
 
             /// <inheritdoc/>
             public void Dispose()
             {
-                byte[]? array = this.array;
+                IntPtr ptr = this.ptr;
 
-                if (array is null)
+                if (ptr == IntPtr.Zero)
                 {
                     return;
                 }
 
-                this.array = null;
+                this.ptr = IntPtr.Zero;
 
-                ArrayPool<byte>.Shared.Return(array);
+                Marshal.FreeHGlobal(ptr);
             }
 
             /// <summary>
             /// Gets the <see cref="Memory{T}"/> for the current instance.
             /// </summary>
-            public Span<T> Span => this.array.AsSpan().Cast<byte, T>().Slice(0, this.length);
+            public Span<T> Span => new Span<T>((void*)this.ptr, this.length);
         }
     }
 }

From db7f64f1340985edc9054f50d29cc0bfa31389ba Mon Sep 17 00:00:00 2001
From: Sergio Pedri <sergio0694@live.com>
Date: Tue, 4 Aug 2020 13:08:28 +0200
Subject: [PATCH 3/8] Minor code refactoring

---
 .../Buffers/Internals/UnmanagedSpanOwner.cs   | 54 +++++++++++++++++++
 .../Test_ReadOnlySpanExtensions.Count.cs      | 50 +----------------
 ...UnitTests.HighPerformance.Shared.projitems |  1 +
 3 files changed, 56 insertions(+), 49 deletions(-)
 create mode 100644 UnitTests/UnitTests.HighPerformance.Shared/Buffers/Internals/UnmanagedSpanOwner.cs

diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Internals/UnmanagedSpanOwner.cs b/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Internals/UnmanagedSpanOwner.cs
new file mode 100644
index 0000000..f821205
--- /dev/null
+++ b/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Internals/UnmanagedSpanOwner.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace UnitTests.HighPerformance.Shared.Buffers.Internals
+{
+    /// <summary>
+    /// An owner for a buffer of an unmanaged type, recycling <see cref="byte"/> arrays to save memory.
+    /// </summary>
+    /// <typeparam name="T">The type of items to store in the rented buffers.</typeparam>
+    internal sealed unsafe class UnmanagedSpanOwner<T> : IDisposable
+        where T : unmanaged
+    {
+        /// <summary>
+        /// The size of the current instance
+        /// </summary>
+        private readonly int length;
+
+        /// <summary>
+        /// The pointer to the underlying <see cref="byte"/> array.
+        /// </summary>
+        private IntPtr ptr;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="UnmanagedSpanOwner{T}"/> class.
+        /// </summary>
+        /// <param name="size">The size of the buffer to rent.</param>
+        public UnmanagedSpanOwner(int size)
+        {
+            this.ptr = Marshal.AllocHGlobal(size * Unsafe.SizeOf<T>());
+            this.length = size;
+        }
+
+        /// <inheritdoc/>
+        public void Dispose()
+        {
+            IntPtr ptr = this.ptr;
+
+            if (ptr == IntPtr.Zero)
+            {
+                return;
+            }
+
+            this.ptr = IntPtr.Zero;
+
+            Marshal.FreeHGlobal(ptr);
+        }
+
+        /// <summary>
+        /// Gets the <see cref="Memory{T}"/> for the current instance.
+        /// </summary>
+        public Span<T> Span => new Span<T>((void*)this.ptr, this.length);
+    }
+}
diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.Count.cs b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.Count.cs
index 4be89dd..28d5948 100644
--- a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.Count.cs
+++ b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.Count.cs
@@ -5,10 +5,10 @@
 using System;
 using System.Diagnostics.CodeAnalysis;
 using System.Diagnostics.Contracts;
-using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
 using Microsoft.Toolkit.HighPerformance.Extensions;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
+using UnitTests.HighPerformance.Shared.Buffers.Internals;
 
 #nullable enable
 
@@ -259,53 +259,5 @@ private static UnmanagedSpanOwner<T> CreateFilledData<T>(int count, T value)
 
             return data;
         }
-
-        /// <summary>
-        /// An owner for a buffer of an unmanaged type, recycling <see cref="byte"/> arrays to save memory.
-        /// </summary>
-        /// <typeparam name="T">The type of items to store in the rented buffers.</typeparam>
-        private sealed unsafe class UnmanagedSpanOwner<T> : IDisposable
-            where T : unmanaged
-        {
-            /// <summary>
-            /// The size of the current instance
-            /// </summary>
-            private readonly int length;
-
-            /// <summary>
-            /// The pointer to the underlying <see cref="byte"/> array.
-            /// </summary>
-            private IntPtr ptr;
-
-            /// <summary>
-            /// Initializes a new instance of the <see cref="UnmanagedSpanOwner{T}"/> class.
-            /// </summary>
-            /// <param name="size">The size of the buffer to rent.</param>
-            public UnmanagedSpanOwner(int size)
-            {
-                this.ptr = Marshal.AllocHGlobal(size * Unsafe.SizeOf<T>());
-                this.length = size;
-            }
-
-            /// <inheritdoc/>
-            public void Dispose()
-            {
-                IntPtr ptr = this.ptr;
-
-                if (ptr == IntPtr.Zero)
-                {
-                    return;
-                }
-
-                this.ptr = IntPtr.Zero;
-
-                Marshal.FreeHGlobal(ptr);
-            }
-
-            /// <summary>
-            /// Gets the <see cref="Memory{T}"/> for the current instance.
-            /// </summary>
-            public Span<T> Span => new Span<T>((void*)this.ptr, this.length);
-        }
     }
 }
diff --git a/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems b/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems
index 06b7503..9539bf3 100644
--- a/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems
+++ b/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems
@@ -9,6 +9,7 @@
     <Import_RootNamespace>UnitTests.HighPerformance.Shared</Import_RootNamespace>
   </PropertyGroup>
   <ItemGroup>
+    <Compile Include="$(MSBuildThisFileDirectory)Buffers\Internals\UnmanagedSpanOwner.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Buffers\Test_MemoryBufferWriter{T}.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Buffers\Test_ArrayPoolBufferWriter{T}.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Buffers\Test_MemoryOwner{T}.cs" />

From 9af5262458675d3af0a381721aa3da9c77cb4c1b Mon Sep 17 00:00:00 2001
From: Sergio Pedri <sergio0694@live.com>
Date: Tue, 4 Aug 2020 13:16:39 +0200
Subject: [PATCH 4/8] Reduced memory usage in hash code tests

---
 .../Helpers/Test_HashCode{T}.cs               | 35 +++++++++++--------
 1 file changed, 20 insertions(+), 15 deletions(-)

diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_HashCode{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_HashCode{T}.cs
index c82ad26..36817d4 100644
--- a/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_HashCode{T}.cs
+++ b/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_HashCode{T}.cs
@@ -8,6 +8,7 @@
 using System.Runtime.InteropServices;
 using Microsoft.Toolkit.HighPerformance.Helpers;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
+using UnitTests.HighPerformance.Shared.Buffers.Internals;
 
 namespace UnitTests.HighPerformance.Helpers
 {
@@ -67,19 +68,23 @@ public void Test_HashCodeOfT_VectorUnsupportedTypes_TestRepeat()
         [TestMethod]
         public void Test_HashCodeOfT_ManagedType_TestRepeat()
         {
+            var localTestCounts = TestCounts.Slice(0, 8);
+
+            // Only rent a single array of the maximum necessary size, to save space
+            string[] data = new string[localTestCounts[localTestCounts.Length - 1]];
+
             var random = new Random();
-
-            foreach (var count in TestCounts.Slice(0, 8))
+            foreach (ref string text in data.AsSpan())
             {
-                string[] data = new string[count];
+                text = random.NextDouble().ToString("E");
+            }
 
-                foreach (ref string text in data.AsSpan())
-                {
-                    text = random.NextDouble().ToString("E");
-                }
+            foreach (var count in localTestCounts)
+            {
+                Span<string> iterationData = data.AsSpan().Slice(0, count);
 
-                int hash1 = HashCode<string>.Combine(data);
-                int hash2 = HashCode<string>.Combine(data);
+                int hash1 = HashCode<string>.Combine(iterationData);
+                int hash2 = HashCode<string>.Combine(iterationData);
 
                 Assert.AreEqual(hash1, hash2, $"Failed {typeof(string)} test with count {count}: got {hash1} and then {hash2}");
             }
@@ -95,10 +100,10 @@ private static void TestForType<T>()
         {
             foreach (var count in TestCounts)
             {
-                T[] data = CreateRandomData<T>(count);
+                using UnmanagedSpanOwner<T> data = CreateRandomData<T>(count);
 
-                int hash1 = HashCode<T>.Combine(data);
-                int hash2 = HashCode<T>.Combine(data);
+                int hash1 = HashCode<T>.Combine(data.Span);
+                int hash2 = HashCode<T>.Combine(data.Span);
 
                 Assert.AreEqual(hash1, hash2, $"Failed {typeof(T)} test with count {count}: got {hash1} and then {hash2}");
             }
@@ -111,14 +116,14 @@ private static void TestForType<T>()
         /// <param name="count">The number of array items to create.</param>
         /// <returns>An array of random <typeparamref name="T"/> elements.</returns>
         [Pure]
-        private static T[] CreateRandomData<T>(int count)
+        private static UnmanagedSpanOwner<T> CreateRandomData<T>(int count)
             where T : unmanaged
         {
             var random = new Random(count);
 
-            T[] data = new T[count];
+            UnmanagedSpanOwner<T> data = new UnmanagedSpanOwner<T>(count);
 
-            foreach (ref byte n in MemoryMarshal.AsBytes(data.AsSpan()))
+            foreach (ref byte n in MemoryMarshal.AsBytes(data.Span))
             {
                 n = (byte)random.Next(0, byte.MaxValue);
             }

From 9ace1401d5a7608e6df4e564005b711f5e0bafaa Mon Sep 17 00:00:00 2001
From: Sergio Pedri <sergio0694@live.com>
Date: Tue, 4 Aug 2020 13:34:19 +0200
Subject: [PATCH 5/8] Added missing file header

---
 .../Buffers/Internals/UnmanagedSpanOwner.cs   | 26 ++++++++++++++-----
 1 file changed, 20 insertions(+), 6 deletions(-)

diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Internals/UnmanagedSpanOwner.cs b/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Internals/UnmanagedSpanOwner.cs
index f821205..7decb59 100644
--- a/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Internals/UnmanagedSpanOwner.cs
+++ b/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Internals/UnmanagedSpanOwner.cs
@@ -1,4 +1,8 @@
-using System;
+// 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.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
 
@@ -31,6 +35,21 @@ public UnmanagedSpanOwner(int size)
             this.length = size;
         }
 
+        /// <summary>
+        /// Gets the length of the buffer in use.
+        /// </summary>
+        public int Length => this.length;
+
+        /// <summary>
+        /// Gets a pointer to the start of the buffer in use.
+        /// </summary>
+        public T* Ptr => (T*)this.ptr;
+
+        /// <summary>
+        /// Gets the <see cref="Memory{T}"/> for the current instance.
+        /// </summary>
+        public Span<T> Span => new Span<T>((void*)this.ptr, this.length);
+
         /// <inheritdoc/>
         public void Dispose()
         {
@@ -45,10 +64,5 @@ public void Dispose()
 
             Marshal.FreeHGlobal(ptr);
         }
-
-        /// <summary>
-        /// Gets the <see cref="Memory{T}"/> for the current instance.
-        /// </summary>
-        public Span<T> Span => new Span<T>((void*)this.ptr, this.length);
     }
 }

From 9dc5481b473cde69a893e41a9d819c1a4524251d Mon Sep 17 00:00:00 2001
From: Sergio Pedri <sergio0694@live.com>
Date: Tue, 4 Aug 2020 13:46:24 +0200
Subject: [PATCH 6/8] Code refactoring to support Memory<T> tests

---
 .../Buffers/Internals/UnmanagedSpanOwner.cs   | 28 ++++++++---
 .../Test_ReadOnlySpanExtensions.Count.cs      | 10 ++--
 .../Helpers/Test_HashCode{T}.cs               |  6 +--
 .../Helpers/Test_ParallelHelper.For.cs        | 43 ++++++++++------
 .../Helpers/Test_ParallelHelper.For2D.cs      | 50 +++++++++++++------
 5 files changed, 92 insertions(+), 45 deletions(-)

diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Internals/UnmanagedSpanOwner.cs b/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Internals/UnmanagedSpanOwner.cs
index 7decb59..70f87a1 100644
--- a/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Internals/UnmanagedSpanOwner.cs
+++ b/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Internals/UnmanagedSpanOwner.cs
@@ -3,6 +3,7 @@
 // See the LICENSE file in the project root for more information.
 
 using System;
+using System.Buffers;
 using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
 
@@ -12,7 +13,7 @@ namespace UnitTests.HighPerformance.Shared.Buffers.Internals
     /// An owner for a buffer of an unmanaged type, recycling <see cref="byte"/> arrays to save memory.
     /// </summary>
     /// <typeparam name="T">The type of items to store in the rented buffers.</typeparam>
-    internal sealed unsafe class UnmanagedSpanOwner<T> : IDisposable
+    internal sealed unsafe class UnmanagedSpanOwner<T> : MemoryManager<T>
         where T : unmanaged
     {
         /// <summary>
@@ -45,13 +46,8 @@ public UnmanagedSpanOwner(int size)
         /// </summary>
         public T* Ptr => (T*)this.ptr;
 
-        /// <summary>
-        /// Gets the <see cref="Memory{T}"/> for the current instance.
-        /// </summary>
-        public Span<T> Span => new Span<T>((void*)this.ptr, this.length);
-
         /// <inheritdoc/>
-        public void Dispose()
+        protected override void Dispose(bool disposing)
         {
             IntPtr ptr = this.ptr;
 
@@ -64,5 +60,23 @@ public void Dispose()
 
             Marshal.FreeHGlobal(ptr);
         }
+
+        /// <inheritdoc/>
+        public override Span<T> GetSpan()
+        {
+            return new Span<T>((void*)this.ptr, this.length);
+        }
+
+        /// <inheritdoc/>
+        public override MemoryHandle Pin(int elementIndex = 0)
+        {
+            throw new NotImplementedException();
+        }
+
+        /// <inheritdoc/>
+        public override void Unpin()
+        {
+            throw new NotImplementedException();
+        }
     }
 }
diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.Count.cs b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.Count.cs
index 28d5948..10f1591 100644
--- a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.Count.cs
+++ b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.Count.cs
@@ -178,8 +178,8 @@ private static void TestForType<T>(T value, Func<int, T, UnmanagedSpanOwner<T>>
             {
                 using UnmanagedSpanOwner<T> data = provider(count, value);
 
-                int result = data.Span.Count(value);
-                int expected = CountWithForeach(data.Span, value);
+                int result = data.GetSpan().Count(value);
+                int expected = CountWithForeach(data.GetSpan(), value);
 
                 Assert.AreEqual(result, expected, $"Failed {typeof(T)} test with count {count}: got {result} instead of {expected}");
             }
@@ -224,7 +224,7 @@ private static UnmanagedSpanOwner<T> CreateRandomData<T>(int count, T value)
 
             UnmanagedSpanOwner<T> data = new UnmanagedSpanOwner<T>(count);
 
-            foreach (ref byte n in MemoryMarshal.AsBytes(data.Span))
+            foreach (ref byte n in MemoryMarshal.AsBytes(data.GetSpan()))
             {
                 n = (byte)random.Next(0, byte.MaxValue);
             }
@@ -232,7 +232,7 @@ private static UnmanagedSpanOwner<T> CreateRandomData<T>(int count, T value)
             // Fill at least 20% of the items with a matching value
             int minimum = count / 20;
 
-            Span<T> span = data.Span;
+            Span<T> span = data.GetSpan();
 
             for (int i = 0; i < minimum; i++)
             {
@@ -255,7 +255,7 @@ private static UnmanagedSpanOwner<T> CreateFilledData<T>(int count, T value)
         {
             UnmanagedSpanOwner<T> data = new UnmanagedSpanOwner<T>(count);
 
-            data.Span.Fill(value);
+            data.GetSpan().Fill(value);
 
             return data;
         }
diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_HashCode{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_HashCode{T}.cs
index 36817d4..8682848 100644
--- a/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_HashCode{T}.cs
+++ b/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_HashCode{T}.cs
@@ -102,8 +102,8 @@ private static void TestForType<T>()
             {
                 using UnmanagedSpanOwner<T> data = CreateRandomData<T>(count);
 
-                int hash1 = HashCode<T>.Combine(data.Span);
-                int hash2 = HashCode<T>.Combine(data.Span);
+                int hash1 = HashCode<T>.Combine(data.GetSpan());
+                int hash2 = HashCode<T>.Combine(data.GetSpan());
 
                 Assert.AreEqual(hash1, hash2, $"Failed {typeof(T)} test with count {count}: got {hash1} and then {hash2}");
             }
@@ -123,7 +123,7 @@ private static UnmanagedSpanOwner<T> CreateRandomData<T>(int count)
 
             UnmanagedSpanOwner<T> data = new UnmanagedSpanOwner<T>(count);
 
-            foreach (ref byte n in MemoryMarshal.AsBytes(data.Span))
+            foreach (ref byte n in MemoryMarshal.AsBytes(data.GetSpan()))
             {
                 n = (byte)random.Next(0, byte.MaxValue);
             }
diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.For.cs b/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.For.cs
index 2fac17d..f1d2b98 100644
--- a/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.For.cs
+++ b/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.For.cs
@@ -6,6 +6,7 @@
 using Microsoft.Toolkit.HighPerformance.Extensions;
 using Microsoft.Toolkit.HighPerformance.Helpers;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
+using UnitTests.HighPerformance.Shared.Buffers.Internals;
 
 namespace UnitTests.HighPerformance.Helpers
 {
@@ -19,15 +20,17 @@ public partial class Test_ParallelHelper
 
         [TestCategory("ParallelHelper")]
         [TestMethod]
-        public void Test_ParallelHelper_ForWithIndices()
+        public unsafe void Test_ParallelHelper_ForWithIndices()
         {
             foreach (int count in TestForCounts)
             {
-                int[] data = new int[count];
+                using UnmanagedSpanOwner<int> data = new UnmanagedSpanOwner<int>(count);
 
-                ParallelHelper.For(0, data.Length, new Assigner(data));
+                data.GetSpan().Clear();
 
-                foreach (var item in data.Enumerate())
+                ParallelHelper.For(0, data.Length, new Assigner(data.Length, data.Ptr));
+
+                foreach (var item in data.GetSpan().Enumerate())
                 {
                     if (item.Index != item.Value)
                     {
@@ -56,15 +59,17 @@ public void Test_ParallelHelper_ForInvalidRange_RangeAll()
 
         [TestCategory("ParallelHelper")]
         [TestMethod]
-        public void Test_ParallelHelper_ForWithRanges()
+        public unsafe void Test_ParallelHelper_ForWithRanges()
         {
             foreach (int count in TestForCounts)
             {
-                int[] data = new int[count];
+                using UnmanagedSpanOwner<int> data = new UnmanagedSpanOwner<int>(count);
 
-                ParallelHelper.For(..data.Length, new Assigner(data));
+                data.GetSpan().Clear();
 
-                foreach (var item in data.Enumerate())
+                ParallelHelper.For(..data.Length, new Assigner(data.Length, data.Ptr));
+
+                foreach (var item in data.GetSpan().Enumerate())
                 {
                     if (item.Index != item.Value)
                     {
@@ -78,21 +83,31 @@ public void Test_ParallelHelper_ForWithRanges()
         /// <summary>
         /// A type implementing <see cref="IAction"/> to initialize an array
         /// </summary>
-        private readonly struct Assigner : IAction
+        private readonly unsafe struct Assigner : IAction
         {
-            private readonly int[] array;
+            private readonly int length;
+            private readonly int* ptr;
 
-            public Assigner(int[] array) => this.array = array;
+            public Assigner(int length, int* ptr)
+            {
+                this.length = length;
+                this.ptr = ptr;
+            }
 
             /// <inheritdoc/>
             public void Invoke(int i)
             {
-                if (this.array[i] != 0)
+                if ((uint)i >= (uint)this.length)
                 {
-                    throw new InvalidOperationException($"Invalid target position {i}, was {this.array[i]} instead of 0");
+                    throw new IndexOutOfRangeException($"The target position was out of range, was {i} and should've been in [0, {this.length})");
                 }
 
-                this.array[i] = i;
+                if (this.ptr[i] != 0)
+                {
+                    throw new InvalidOperationException($"Invalid target position {i}, was {this.ptr[i]} instead of 0");
+                }
+
+                this.ptr[i] = i;
             }
         }
     }
diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.For2D.cs b/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.For2D.cs
index 8fccecf..e038e2c 100644
--- a/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.For2D.cs
+++ b/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.For2D.cs
@@ -6,6 +6,7 @@
 using System.Drawing;
 using Microsoft.Toolkit.HighPerformance.Helpers;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
+using UnitTests.HighPerformance.Shared.Buffers.Internals;
 
 namespace UnitTests.HighPerformance.Helpers
 {
@@ -27,21 +28,23 @@ public partial class Test_ParallelHelper
 
         [TestCategory("ParallelHelper")]
         [TestMethod]
-        public void Test_ParallelHelper_For2DWithIndices()
+        public unsafe void Test_ParallelHelper_For2DWithIndices()
         {
             foreach (var size in TestFor2DSizes)
             {
-                int[,] data = new int[size.Height, size.Width];
+                using UnmanagedSpanOwner<int> data = new UnmanagedSpanOwner<int>(size.Height * size.Width);
 
-                ParallelHelper.For2D(0, size.Height, 0, size.Width, new Assigner2D(data));
+                data.GetSpan().Clear();
+
+                ParallelHelper.For2D(0, size.Height, 0, size.Width, new Assigner2D(size.Height, size.Width, data.Ptr));
 
                 for (int i = 0; i < size.Height; i++)
                 {
                     for (int j = 0; j < size.Width; j++)
                     {
-                        if (data[i, j] != unchecked(i * 397 ^ j))
+                        if (data.Ptr[(i * size.Width) + j] != unchecked(i * 397 ^ j))
                         {
-                            Assert.Fail($"Invalid item at position [{i},{j}], value was {data[i, j]} instead of {unchecked(i * 397 ^ j)}");
+                            Assert.Fail($"Invalid item at position [{i},{j}], value was {data.Ptr[(i * size.Width) + j]} instead of {unchecked(i * 397 ^ j)}");
                         }
                     }
                 }
@@ -67,21 +70,23 @@ public void Test_ParallelHelper_For2DInvalidRange_RangeAll()
 
         [TestCategory("ParallelHelper")]
         [TestMethod]
-        public void Test_ParallelHelper_For2DWithRanges()
+        public unsafe void Test_ParallelHelper_For2DWithRanges()
         {
             foreach (var size in TestFor2DSizes)
             {
-                int[,] data = new int[size.Height, size.Width];
+                using UnmanagedSpanOwner<int> data = new UnmanagedSpanOwner<int>(size.Height * size.Width);
 
-                ParallelHelper.For2D(..size.Height, ..size.Width, new Assigner2D(data));
+                data.GetSpan().Clear();
+
+                ParallelHelper.For2D(..size.Height, ..size.Width, new Assigner2D(size.Height, size.Width, data.Ptr));
 
                 for (int i = 0; i < size.Height; i++)
                 {
                     for (int j = 0; j < size.Width; j++)
                     {
-                        if (data[i, j] != unchecked(i * 397 ^ j))
+                        if (data.Ptr[(i * size.Width) + j] != unchecked(i * 397 ^ j))
                         {
-                            Assert.Fail($"Invalid item at position [{i},{j}], value was {data[i, j]} instead of {unchecked(i * 397 ^ j)}");
+                            Assert.Fail($"Invalid item at position [{i},{j}], value was {data.Ptr[(i * size.Width) + j]} instead of {unchecked(i * 397 ^ j)}");
                         }
                     }
                 }
@@ -92,21 +97,34 @@ public void Test_ParallelHelper_For2DWithRanges()
         /// <summary>
         /// A type implementing <see cref="IAction"/> to initialize a 2D array
         /// </summary>
-        private readonly struct Assigner2D : IAction2D
+        private readonly unsafe struct Assigner2D : IAction2D
         {
-            private readonly int[,] array;
+            private readonly int height;
+            private readonly int width;
+            private readonly int* ptr;
 
-            public Assigner2D(int[,] array) => this.array = array;
+            public Assigner2D(int height, int width, int* ptr)
+            {
+                this.height = height;
+                this.width = width;
+                this.ptr = ptr;
+            }
 
             /// <inheritdoc/>
             public void Invoke(int i, int j)
             {
-                if (this.array[i, j] != 0)
+                if ((uint)i >= (uint)this.height ||
+                    (uint)j >= (uint)this.width)
                 {
-                    throw new InvalidOperationException($"Invalid target position [{i},{j}], was {this.array[i, j]} instead of 0");
+                    throw new IndexOutOfRangeException($"The target position was invalid, was [{i}, {j}], should've been in [0, {this.height}] and [0, {this.width}]");
                 }
 
-                this.array[i, j] = unchecked(i * 397 ^ j);
+                if (this.ptr[(i * this.width) + j] != 0)
+                {
+                    throw new InvalidOperationException($"Invalid target position [{i},{j}], was {this.ptr[(i * this.width) + j]} instead of 0");
+                }
+
+                this.ptr[(i * this.width) + j] = unchecked(i * 397 ^ j);
             }
         }
     }

From 8cf39a14d7be7e702358da3a3b0b68a0fa1b94af Mon Sep 17 00:00:00 2001
From: Sergio Pedri <sergio0694@live.com>
Date: Tue, 4 Aug 2020 13:51:49 +0200
Subject: [PATCH 7/8] Switched to unmanaged memory in all parallel tests

---
 .../Extensions/Test_ReadOnlySpanExtensions.cs  |  6 +++---
 .../Helpers/Test_ParallelHelper.ForEach.In.cs  | 13 +++++++------
 .../Helpers/Test_ParallelHelper.ForEach.Ref.cs | 18 ++++++++++++------
 3 files changed, 22 insertions(+), 15 deletions(-)

diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.cs b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.cs
index c431e49..80b6b4d 100644
--- a/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.cs
+++ b/UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.cs
@@ -20,7 +20,7 @@ public void Test_ReadOnlySpanExtensions_DangerousGetReference()
         {
             using var owner = CreateRandomData<int>(12, default);
 
-            ReadOnlySpan<int> data = owner.Span;
+            ReadOnlySpan<int> data = owner.GetSpan();
 
             ref int r0 = ref data.DangerousGetReference();
             ref int r1 = ref Unsafe.AsRef(data[0]);
@@ -34,7 +34,7 @@ public void Test_ReadOnlySpanExtensions_DangerousGetReferenceAt_Zero()
         {
             using var owner = CreateRandomData<int>(12, default);
 
-            ReadOnlySpan<int> data = owner.Span;
+            ReadOnlySpan<int> data = owner.GetSpan();
 
             ref int r0 = ref data.DangerousGetReference();
             ref int r1 = ref data.DangerousGetReferenceAt(0);
@@ -48,7 +48,7 @@ public void Test_ReadOnlySpanExtensions_DangerousGetReferenceAt_Index()
         {
             using var owner = CreateRandomData<int>(12, default);
 
-            ReadOnlySpan<int> data = owner.Span;
+            ReadOnlySpan<int> data = owner.GetSpan();
 
             ref int r0 = ref data.DangerousGetReferenceAt(5);
             ref int r1 = ref Unsafe.AsRef(data[5]);
diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.ForEach.In.cs b/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.ForEach.In.cs
index b53a97b..b7d49e5 100644
--- a/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.ForEach.In.cs
+++ b/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.ForEach.In.cs
@@ -8,6 +8,7 @@
 using System.Threading;
 using Microsoft.Toolkit.HighPerformance.Helpers;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
+using UnitTests.HighPerformance.Shared.Buffers.Internals;
 
 namespace UnitTests.HighPerformance.Helpers
 {
@@ -19,15 +20,15 @@ public unsafe void Test_ParallelHelper_ForEach_In()
         {
             foreach (int count in TestForCounts)
             {
-                int[] data = CreateRandomData(count);
+                using UnmanagedSpanOwner<int> data = CreateRandomData(count);
 
                 int sum = 0;
 
-                ParallelHelper.ForEach<int, Summer>(data.AsMemory(), new Summer(&sum));
+                ParallelHelper.ForEach<int, Summer>(data.Memory, new Summer(&sum));
 
                 int expected = 0;
 
-                foreach (int n in data)
+                foreach (int n in data.GetSpan())
                 {
                     expected += n;
                 }
@@ -55,13 +56,13 @@ public unsafe void Test_ParallelHelper_ForEach_In()
         /// <param name="count">The number of array items to create.</param>
         /// <returns>An array of random <see cref="int"/> elements.</returns>
         [Pure]
-        private static int[] CreateRandomData(int count)
+        private static UnmanagedSpanOwner<int> CreateRandomData(int count)
         {
             var random = new Random(count);
 
-            int[] data = new int[count];
+            UnmanagedSpanOwner<int> data = new UnmanagedSpanOwner<int>(count);
 
-            foreach (ref int n in data.AsSpan())
+            foreach (ref int n in data.GetSpan())
             {
                 n = random.Next(0, byte.MaxValue);
             }
diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.ForEach.Ref.cs b/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.ForEach.Ref.cs
index cccfbb7..0571fae 100644
--- a/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.ForEach.Ref.cs
+++ b/UnitTests/UnitTests.HighPerformance.Shared/Helpers/Test_ParallelHelper.ForEach.Ref.cs
@@ -5,6 +5,7 @@
 using System;
 using Microsoft.Toolkit.HighPerformance.Helpers;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
+using UnitTests.HighPerformance.Shared.Buffers.Internals;
 
 namespace UnitTests.HighPerformance.Helpers
 {
@@ -16,21 +17,26 @@ public void Test_ParallelHelper_ForEach_Ref()
         {
             foreach (int count in TestForCounts)
             {
-                int[] data = CreateRandomData(count);
-                int[] copy = data.AsSpan().ToArray();
+                using UnmanagedSpanOwner<int> data = CreateRandomData(count);
+                using UnmanagedSpanOwner<int> copy = new UnmanagedSpanOwner<int>(count);
 
-                foreach (ref int n in copy.AsSpan())
+                data.GetSpan().CopyTo(copy.GetSpan());
+
+                foreach (ref int n in copy.GetSpan())
                 {
                     n = unchecked(n * 397);
                 }
 
-                ParallelHelper.ForEach(data.AsMemory(), new Multiplier(397));
+                ParallelHelper.ForEach(data.Memory, new Multiplier(397));
+
+                Span<int> dataSpan = data.GetSpan();
+                Span<int> copySpan = copy.GetSpan();
 
                 for (int i = 0; i < data.Length; i++)
                 {
-                    if (data[i] != copy[i])
+                    if (dataSpan[i] != copySpan[i])
                     {
-                        Assert.Fail($"Item #{i} was not a match, was {data[i]} instead of {copy[i]}");
+                        Assert.Fail($"Item #{i} was not a match, was {dataSpan[i]} instead of {copySpan[i]}");
                     }
                 }
             }

From 6f4b9c9f9fa750afa826a05c45488101704bffd0 Mon Sep 17 00:00:00 2001
From: Kyaa Dost <35208324+Kyaa-dost@users.noreply.github.com>
Date: Wed, 5 Aug 2020 17:25:36 -0700
Subject: [PATCH 8/8] Update Microsoft.Toolkit.HighPerformance.csproj

---
 .../Microsoft.Toolkit.HighPerformance.csproj                  | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Microsoft.Toolkit.HighPerformance/Microsoft.Toolkit.HighPerformance.csproj b/Microsoft.Toolkit.HighPerformance/Microsoft.Toolkit.HighPerformance.csproj
index f1d961a..a0d6bcb 100644
--- a/Microsoft.Toolkit.HighPerformance/Microsoft.Toolkit.HighPerformance.csproj
+++ b/Microsoft.Toolkit.HighPerformance/Microsoft.Toolkit.HighPerformance.csproj
@@ -78,9 +78,9 @@
 
         <!-- NETCORE_RUNTIME: to avoid issues with APIs that assume a specific memory layout, we define a
              .NET Core runtime constant to indicate the either .NET Core 2.1 or .NET Core 3.1. These are
-             runtimes with the same overall memory layout for objects (in particular: strings, SZ arrays
+             runtimes with the same overall memory layout for objects (in particular: strings, SZ arrays,
              and 2D arrays). We can use this constant to make sure that APIs that are exclusively available
-             for .NET Standard targets do not make any assumtpion of any internals of the runtime being
+             for .NET Standard targets do not make any assumption of any internals of the runtime being
              actually used by the consumers. -->
         <DefineConstants>NETSTANDARD2_1_OR_GREATER;SPAN_RUNTIME_SUPPORT;NETCORE_RUNTIME</DefineConstants>
       </PropertyGroup>