From b464ce2dca78c122bb21cd4d8e99d7c972cd2f82 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 16 Jun 2020 16:51:15 +0200 Subject: [PATCH 01/16] Removed unnecessary attributes, improved codegen --- Microsoft.Toolkit.HighPerformance/Box{T}.cs | 1 - .../Buffers/ArrayPoolBufferWriter{T}.cs | 5 ----- .../Buffers/MemoryBufferWriter{T}.cs | 4 ---- .../Buffers/MemoryOwner{T}.cs | 3 --- .../Enumerables/Array2DColumnEnumerable{T}.cs | 1 - .../Enumerables/Array2DRowEnumerable{T}.cs | 1 - .../Helpers/ParallelHelper.ThrowExceptions.cs | 6 ------ .../NullableReadOnlyRef{T}.cs | 1 - Microsoft.Toolkit.HighPerformance/NullableRef{T}.cs | 1 - .../Streams/MemoryStream.ThrowExceptions.cs | 11 ----------- 10 files changed, 34 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Box{T}.cs b/Microsoft.Toolkit.HighPerformance/Box{T}.cs index d6c72753453..73de5507d3d 100644 --- a/Microsoft.Toolkit.HighPerformance/Box{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Box{T}.cs @@ -180,7 +180,6 @@ public override int GetHashCode() /// /// Throws an when a cast from an invalid is attempted. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowInvalidCastExceptionForGetFrom() { throw new InvalidCastException($"Can't cast the input object to the type Box<{typeof(T)}>"); diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/ArrayPoolBufferWriter{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/ArrayPoolBufferWriter{T}.cs index 1f2b1b3e600..38ddeaef104 100644 --- a/Microsoft.Toolkit.HighPerformance/Buffers/ArrayPoolBufferWriter{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Buffers/ArrayPoolBufferWriter{T}.cs @@ -289,7 +289,6 @@ public override string ToString() /// /// Throws an when the initial capacity is invalid. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentOutOfRangeExceptionForInitialCapacity() { throw new ArgumentOutOfRangeException("initialCapacity", "The initial capacity must be a positive value"); @@ -298,7 +297,6 @@ private static void ThrowArgumentOutOfRangeExceptionForInitialCapacity() /// /// Throws an when the requested count is negative. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentOutOfRangeExceptionForNegativeCount() { throw new ArgumentOutOfRangeException("count", "The count can't be a negative value"); @@ -307,7 +305,6 @@ private static void ThrowArgumentOutOfRangeExceptionForNegativeCount() /// /// Throws an when the size hint is negative. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentOutOfRangeExceptionForNegativeSizeHint() { throw new ArgumentOutOfRangeException("sizeHint", "The size hint can't be a negative value"); @@ -316,7 +313,6 @@ private static void ThrowArgumentOutOfRangeExceptionForNegativeSizeHint() /// /// Throws an when the requested count is negative. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentExceptionForAdvancedTooFar() { throw new ArgumentException("The buffer writer has advanced too far"); @@ -325,7 +321,6 @@ private static void ThrowArgumentExceptionForAdvancedTooFar() /// /// Throws an when is . /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowObjectDisposedException() { throw new ObjectDisposedException("The current buffer has already been disposed"); diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/MemoryBufferWriter{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/MemoryBufferWriter{T}.cs index ec0c0fcc5d9..b67b578e4ed 100644 --- a/Microsoft.Toolkit.HighPerformance/Buffers/MemoryBufferWriter{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Buffers/MemoryBufferWriter{T}.cs @@ -159,7 +159,6 @@ public override string ToString() /// /// Throws an when the requested count is negative. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentOutOfRangeExceptionForNegativeCount() { throw new ArgumentOutOfRangeException("count", "The count can't be a negative value"); @@ -168,7 +167,6 @@ private static void ThrowArgumentOutOfRangeExceptionForNegativeCount() /// /// Throws an when the size hint is negative. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentOutOfRangeExceptionForNegativeSizeHint() { throw new ArgumentOutOfRangeException("sizeHint", "The size hint can't be a negative value"); @@ -177,7 +175,6 @@ private static void ThrowArgumentOutOfRangeExceptionForNegativeSizeHint() /// /// Throws an when the requested count is negative. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentExceptionForAdvancedTooFar() { throw new ArgumentException("The buffer writer has advanced too far"); @@ -186,7 +183,6 @@ private static void ThrowArgumentExceptionForAdvancedTooFar() /// /// Throws an when the requested size exceeds the capacity. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentExceptionForCapacityExceeded() { throw new ArgumentException("The buffer writer doesn't have enough capacity left"); diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/MemoryOwner{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/MemoryOwner{T}.cs index 38bd7382267..825062b45e8 100644 --- a/Microsoft.Toolkit.HighPerformance/Buffers/MemoryOwner{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Buffers/MemoryOwner{T}.cs @@ -251,7 +251,6 @@ public override string ToString() /// /// Throws an when is . /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowObjectDisposedException() { throw new ObjectDisposedException(nameof(MemoryOwner), "The current buffer has already been disposed"); @@ -260,7 +259,6 @@ private static void ThrowObjectDisposedException() /// /// Throws an when the is invalid. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowInvalidOffsetException() { throw new ArgumentOutOfRangeException(nameof(start), "The input start parameter was not valid"); @@ -269,7 +267,6 @@ private static void ThrowInvalidOffsetException() /// /// Throws an when the is invalid. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowInvalidLengthException() { throw new ArgumentOutOfRangeException(nameof(length), "The input length parameter was not valid"); diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/Array2DColumnEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/Array2DColumnEnumerable{T}.cs index 6cd0ceaf9f3..629d5f903f8 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/Array2DColumnEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/Array2DColumnEnumerable{T}.cs @@ -205,7 +205,6 @@ public ref T Current /// /// Throws an when the is invalid. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentOutOfRangeExceptionForInvalidColumn() { throw new ArgumentOutOfRangeException(nameof(column), "The target column parameter was not valid"); diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/Array2DRowEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/Array2DRowEnumerable{T}.cs index a0d1fe98f5c..5c146d5a5e8 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/Array2DRowEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/Array2DRowEnumerable{T}.cs @@ -160,7 +160,6 @@ public ref T Current /// /// Throws an when the is invalid. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentOutOfRangeExceptionForInvalidRow() { throw new ArgumentOutOfRangeException(nameof(row), "The target row parameter was not valid"); diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ThrowExceptions.cs b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ThrowExceptions.cs index f5b0d6c1b90..b0701f932d3 100644 --- a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ThrowExceptions.cs +++ b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ThrowExceptions.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Runtime.CompilerServices; namespace Microsoft.Toolkit.HighPerformance.Helpers { @@ -15,7 +14,6 @@ public static partial class ParallelHelper /// /// Throws an when an invalid parameter is specified for the minimum actions per thread. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentOutOfRangeExceptionForInvalidMinimumActionsPerThread() { // Having the argument name here manually typed is @@ -30,7 +28,6 @@ private static void ThrowArgumentOutOfRangeExceptionForInvalidMinimumActionsPerT /// /// Throws an when an invalid start parameter is specified for 1D loops. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentOutOfRangeExceptionForStartGreaterThanEnd() { throw new ArgumentOutOfRangeException("start", "The start parameter must be less than or equal to end"); @@ -39,7 +36,6 @@ private static void ThrowArgumentOutOfRangeExceptionForStartGreaterThanEnd() /// /// Throws an when a range has an index starting from an end. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentExceptionForRangeIndexFromEnd(string name) { throw new ArgumentException("The bounds of the range can't start from an end", name); @@ -48,7 +44,6 @@ private static void ThrowArgumentExceptionForRangeIndexFromEnd(string name) /// /// Throws an when an invalid top parameter is specified for 2D loops. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentOutOfRangeExceptionForTopGreaterThanBottom() { throw new ArgumentOutOfRangeException("top", "The top parameter must be less than or equal to bottom"); @@ -57,7 +52,6 @@ private static void ThrowArgumentOutOfRangeExceptionForTopGreaterThanBottom() /// /// Throws an when an invalid left parameter is specified for 2D loops. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentOutOfRangeExceptionForLeftGreaterThanRight() { throw new ArgumentOutOfRangeException("left", "The left parameter must be less than or equal to right"); diff --git a/Microsoft.Toolkit.HighPerformance/NullableReadOnlyRef{T}.cs b/Microsoft.Toolkit.HighPerformance/NullableReadOnlyRef{T}.cs index 76a685d74eb..1742bfcad21 100644 --- a/Microsoft.Toolkit.HighPerformance/NullableReadOnlyRef{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/NullableReadOnlyRef{T}.cs @@ -129,7 +129,6 @@ public static explicit operator T(NullableReadOnlyRef reference) /// /// Throws a when trying to access for a default instance. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowInvalidOperationException() { throw new InvalidOperationException("The current instance doesn't have a value that can be accessed"); diff --git a/Microsoft.Toolkit.HighPerformance/NullableRef{T}.cs b/Microsoft.Toolkit.HighPerformance/NullableRef{T}.cs index ee4fc9c8750..c4a3080665b 100644 --- a/Microsoft.Toolkit.HighPerformance/NullableRef{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/NullableRef{T}.cs @@ -113,7 +113,6 @@ public static explicit operator T(NullableRef reference) /// /// Throws a when trying to access for a default instance. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowInvalidOperationException() { throw new InvalidOperationException("The current instance doesn't have a value that can be accessed"); diff --git a/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.ThrowExceptions.cs b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.ThrowExceptions.cs index 1d44a481b50..209c935e4a5 100644 --- a/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.ThrowExceptions.cs +++ b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.ThrowExceptions.cs @@ -4,7 +4,6 @@ using System; using System.IO; -using System.Runtime.CompilerServices; namespace Microsoft.Toolkit.HighPerformance.Streams { @@ -16,7 +15,6 @@ internal partial class MemoryStream /// /// Throws an when setting the property. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentOutOfRangeExceptionForPosition() { throw new ArgumentOutOfRangeException(nameof(Position), "The value for the property was not in the valid range."); @@ -25,7 +23,6 @@ private static void ThrowArgumentOutOfRangeExceptionForPosition() /// /// Throws an when an input buffer is . /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentNullExceptionForBuffer() { throw new ArgumentNullException("buffer", "The buffer is null."); @@ -34,7 +31,6 @@ private static void ThrowArgumentNullExceptionForBuffer() /// /// Throws an when the input count is negative. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentOutOfRangeExceptionForOffset() { throw new ArgumentOutOfRangeException("offset", "Offset can't be negative."); @@ -43,7 +39,6 @@ private static void ThrowArgumentOutOfRangeExceptionForOffset() /// /// Throws an when the input count is negative. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentOutOfRangeExceptionForCount() { throw new ArgumentOutOfRangeException("count", "Count can't be negative."); @@ -52,7 +47,6 @@ private static void ThrowArgumentOutOfRangeExceptionForCount() /// /// Throws an when the sum of offset and count exceeds the length of the target buffer. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentExceptionForLength() { throw new ArgumentException("The sum of offset and count can't be larger than the buffer length.", "buffer"); @@ -61,7 +55,6 @@ private static void ThrowArgumentExceptionForLength() /// /// Throws a when trying to write on a readonly stream. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowNotSupportedExceptionForCanWrite() { throw new NotSupportedException("The current stream doesn't support writing."); @@ -70,7 +63,6 @@ private static void ThrowNotSupportedExceptionForCanWrite() /// /// Throws an when trying to write too many bytes to the target stream. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentExceptionForEndOfStreamOnWrite() { throw new ArgumentException("The current stream can't contain the requested input data."); @@ -79,7 +71,6 @@ private static void ThrowArgumentExceptionForEndOfStreamOnWrite() /// /// Throws a when trying to set the length of the stream. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowNotSupportedExceptionForSetLength() { throw new NotSupportedException("Setting the length is not supported for this stream."); @@ -89,7 +80,6 @@ private static void ThrowNotSupportedExceptionForSetLength() /// Throws an when using an invalid seek mode. /// /// Nothing, as this method throws unconditionally. - [MethodImpl(MethodImplOptions.NoInlining)] private static long ThrowArgumentExceptionForSeekOrigin() { throw new ArgumentException("The input seek mode is not valid.", "origin"); @@ -98,7 +88,6 @@ private static long ThrowArgumentExceptionForSeekOrigin() /// /// Throws an when using a disposed instance. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowObjectDisposedException() { throw new ObjectDisposedException(nameof(memory), "The current stream has already been disposed"); From 301b5fa3ca52f15fb08b9ce77826fcf967281c22 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 16 Jun 2020 17:16:18 +0200 Subject: [PATCH 02/16] Added custom ArrayPool field to Memoryowner --- .../Buffers/MemoryOwner{T}.cs | 54 +++++++++++++++---- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/MemoryOwner{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/MemoryOwner{T}.cs index 825062b45e8..b243a5fdb5d 100644 --- a/Microsoft.Toolkit.HighPerformance/Buffers/MemoryOwner{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Buffers/MemoryOwner{T}.cs @@ -32,6 +32,11 @@ public sealed class MemoryOwner : IMemoryOwner private readonly int length; #pragma warning restore IDE0032 + /// + /// The instance used to rent . + /// + private readonly ArrayPool pool; + /// /// The underlying array. /// @@ -41,12 +46,14 @@ public sealed class MemoryOwner : IMemoryOwner /// Initializes a new instance of the class with the specified parameters. /// /// The length of the new memory buffer to use. + /// The instance to use. /// Indicates the allocation mode to use for the new buffer to rent. - private MemoryOwner(int length, AllocationMode mode) + private MemoryOwner(int length, ArrayPool pool, AllocationMode mode) { this.start = 0; this.length = length; - this.array = ArrayPool.Shared.Rent(length); + this.pool = pool; + this.array = pool.Rent(length); if (mode == AllocationMode.Clear) { @@ -57,20 +64,22 @@ private MemoryOwner(int length, AllocationMode mode) /// /// Initializes a new instance of the class with the specified parameters. /// - /// The input array to use. /// The starting offset within . /// The length of the array to use. - private MemoryOwner(T[] array, int start, int length) + /// The instance currently in use. + /// The input array to use. + private MemoryOwner(int start, int length, ArrayPool pool, T[] array) { this.start = start; this.length = length; + this.pool = pool; this.array = array; } /// /// Finalizes an instance of the class. /// - ~MemoryOwner() => this.Dispose(); + ~MemoryOwner() => Dispose(); /// /// Gets an empty instance. @@ -79,7 +88,7 @@ private MemoryOwner(T[] array, int start, int length) public static MemoryOwner Empty { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => new MemoryOwner(0, AllocationMode.Default); + get => new MemoryOwner(0, ArrayPool.Shared, AllocationMode.Default); } /// @@ -91,19 +100,44 @@ public static MemoryOwner Empty /// This method is just a proxy for the constructor, for clarity. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static MemoryOwner Allocate(int size) => new MemoryOwner(size, AllocationMode.Default); + public static MemoryOwner Allocate(int size) => new MemoryOwner(size, ArrayPool.Shared, AllocationMode.Default); + + /// + /// Creates a new instance with the specified parameters. + /// + /// The length of the new memory buffer to use. + /// The instance currently in use. + /// A instance of the requested length. + /// Thrown when is not valid. + /// This method is just a proxy for the constructor, for clarity. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MemoryOwner Allocate(int size, ArrayPool pool) => new MemoryOwner(size, pool, AllocationMode.Default); + + /// + /// Creates a new instance with the specified parameters. + /// + /// The length of the new memory buffer to use. + /// Indicates the allocation mode to use for the new buffer to rent. + /// A instance of the requested length. + /// Thrown when is not valid. + /// This method is just a proxy for the constructor, for clarity. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MemoryOwner Allocate(int size, AllocationMode mode) => new MemoryOwner(size, ArrayPool.Shared, mode); /// /// Creates a new instance with the specified parameters. /// /// The length of the new memory buffer to use. + /// The instance currently in use. /// Indicates the allocation mode to use for the new buffer to rent. /// A instance of the requested length. /// Thrown when is not valid. /// This method is just a proxy for the constructor, for clarity. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static MemoryOwner Allocate(int size, AllocationMode mode) => new MemoryOwner(size, mode); + public static MemoryOwner Allocate(int size, ArrayPool pool, AllocationMode mode) => new MemoryOwner(size, pool, mode); /// /// Gets the number of items in the current instance @@ -210,7 +244,7 @@ public MemoryOwner Slice(int start, int length) ThrowInvalidLengthException(); } - return new MemoryOwner(array!, start, length); + return new MemoryOwner(start, length, this.pool, array!); } /// @@ -227,7 +261,7 @@ public void Dispose() this.array = null; - ArrayPool.Shared.Return(array); + this.pool.Return(array); } /// From 6bd669fdd26de44656f22edb9ef8842f4591a09e Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 16 Jun 2020 17:24:06 +0200 Subject: [PATCH 03/16] Added custom pool to ArrayPoolBufferWriter --- .../Buffers/ArrayPoolBufferWriter{T}.cs | 46 +++++++++++++++---- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/ArrayPoolBufferWriter{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/ArrayPoolBufferWriter{T}.cs index 38ddeaef104..1f884d69ab2 100644 --- a/Microsoft.Toolkit.HighPerformance/Buffers/ArrayPoolBufferWriter{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Buffers/ArrayPoolBufferWriter{T}.cs @@ -33,6 +33,11 @@ public sealed class ArrayPoolBufferWriter : IBuffer, IMemoryOwner /// private const int DefaultInitialBufferSize = 256; + /// + /// The instance used to rent . + /// + private readonly ArrayPool pool; + /// /// The underlying array. /// @@ -49,12 +54,17 @@ public sealed class ArrayPoolBufferWriter : IBuffer, IMemoryOwner /// Initializes a new instance of the class. /// public ArrayPoolBufferWriter() + : this(ArrayPool.Shared, DefaultInitialBufferSize) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The instance to use. + public ArrayPoolBufferWriter(ArrayPool pool) + : this(pool, DefaultInitialBufferSize) { - // Since we're using pooled arrays, we can rent the buffer with the - // default size immediately, we don't need to use lazy initialization - // to save unnecessary memory allocations in this case. - this.array = ArrayPool.Shared.Rent(DefaultInitialBufferSize); - this.index = 0; } /// @@ -65,20 +75,37 @@ public ArrayPoolBufferWriter() /// Thrown when is not positive (i.e. less than or equal to 0). /// public ArrayPoolBufferWriter(int initialCapacity) + : this(ArrayPool.Shared, initialCapacity) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The instance to use. + /// The minimum capacity with which to initialize the underlying buffer. + /// + /// Thrown when is not positive (i.e. less than or equal to 0). + /// + public ArrayPoolBufferWriter(ArrayPool pool, int initialCapacity) { if (initialCapacity <= 0) { ThrowArgumentOutOfRangeExceptionForInitialCapacity(); } - this.array = ArrayPool.Shared.Rent(initialCapacity); + // Since we're using pooled arrays, we can rent the buffer with the + // default size immediately, we don't need to use lazy initialization + // to save unnecessary memory allocations in this case. + this.pool = pool; + this.array = pool.Rent(initialCapacity); this.index = 0; } /// /// Finalizes an instance of the class. /// - ~ArrayPoolBufferWriter() => this.Dispose(); + ~ArrayPoolBufferWriter() => Dispose(); /// Memory IMemoryOwner.Memory @@ -182,6 +209,7 @@ public void Clear() } array.AsSpan(0, this.index).Clear(); + this.index = 0; } @@ -250,7 +278,7 @@ private void CheckAndResizeBuffer(int sizeHint) { int minimumSize = this.index + sizeHint; - ArrayPool.Shared.Resize(ref this.array, minimumSize); + this.pool.Resize(ref this.array, minimumSize); } } @@ -268,7 +296,7 @@ public void Dispose() this.array = null; - ArrayPool.Shared.Return(array); + this.pool.Return(array); } /// From d9b07d773d6254f2eac8cf599beadc05ea7bd461 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 16 Jun 2020 17:25:40 +0200 Subject: [PATCH 04/16] Minor codegen improvement to Box --- Microsoft.Toolkit.HighPerformance/Box{T}.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.HighPerformance/Box{T}.cs b/Microsoft.Toolkit.HighPerformance/Box{T}.cs index 73de5507d3d..df6ad3eca83 100644 --- a/Microsoft.Toolkit.HighPerformance/Box{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Box{T}.cs @@ -125,7 +125,7 @@ public static bool TryGetFrom(object obj, [NotNullWhen(true)] out Box? box) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator T(Box box) { - return Unsafe.Unbox(box); + return (T)(object)box; } /// From a2217fa21d910b7d36b5b3d94cc634fea6a6334e Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 16 Jun 2020 17:37:30 +0200 Subject: [PATCH 05/16] Added custom pool to SpanwOwner --- .../Buffers/SpanOwner{T}.cs | 48 +++++++++++++++---- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/SpanOwner{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/SpanOwner{T}.cs index a8d3fbfb183..481f60f8195 100644 --- a/Microsoft.Toolkit.HighPerformance/Buffers/SpanOwner{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Buffers/SpanOwner{T}.cs @@ -42,6 +42,11 @@ public readonly ref struct SpanOwner private readonly int length; #pragma warning restore IDE0032 + /// + /// The instance used to rent . + /// + private readonly ArrayPool pool; + /// /// The underlying array. /// @@ -51,11 +56,13 @@ public readonly ref struct SpanOwner /// Initializes a new instance of the struct with the specified parameters. /// /// The length of the new memory buffer to use. + /// The instance to use. /// Indicates the allocation mode to use for the new buffer to rent. - private SpanOwner(int length, AllocationMode mode) + private SpanOwner(int length, ArrayPool pool, AllocationMode mode) { this.length = length; - this.array = ArrayPool.Shared.Rent(length); + this.pool = pool; + this.array = pool.Rent(length); if (mode == AllocationMode.Clear) { @@ -70,7 +77,7 @@ private SpanOwner(int length, AllocationMode mode) public static SpanOwner Empty { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => new SpanOwner(0, AllocationMode.Default); + get => new SpanOwner(0, ArrayPool.Shared, AllocationMode.Default); } /// @@ -82,19 +89,44 @@ public static SpanOwner Empty /// This method is just a proxy for the constructor, for clarity. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static SpanOwner Allocate(int size) => new SpanOwner(size, AllocationMode.Default); + public static SpanOwner Allocate(int size) => new SpanOwner(size, ArrayPool.Shared, AllocationMode.Default); + + /// + /// Creates a new instance with the specified parameters. + /// + /// The length of the new memory buffer to use. + /// The instance to use. + /// A instance of the requested length. + /// Thrown when is not valid. + /// This method is just a proxy for the constructor, for clarity. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static SpanOwner Allocate(int size, ArrayPool pool) => new SpanOwner(size, pool, AllocationMode.Default); + + /// + /// Creates a new instance with the specified parameters. + /// + /// The length of the new memory buffer to use. + /// Indicates the allocation mode to use for the new buffer to rent. + /// A instance of the requested length. + /// Thrown when is not valid. + /// This method is just a proxy for the constructor, for clarity. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static SpanOwner Allocate(int size, AllocationMode mode) => new SpanOwner(size, ArrayPool.Shared, mode); /// /// Creates a new instance with the specified parameters. /// /// The length of the new memory buffer to use. + /// The instance to use. /// Indicates the allocation mode to use for the new buffer to rent. /// A instance of the requested length. /// Thrown when is not valid. /// This method is just a proxy for the constructor, for clarity. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static SpanOwner Allocate(int size, AllocationMode mode) => new SpanOwner(size, mode); + public static SpanOwner Allocate(int size, ArrayPool pool, AllocationMode mode) => new SpanOwner(size, pool, mode); /// /// Gets the number of items in the current instance @@ -111,7 +143,7 @@ public int Length public Span Span { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => new Span(array, 0, this.length); + get => new Span(this.array, 0, this.length); } /// @@ -122,7 +154,7 @@ public Span Span [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref T DangerousGetReference() { - return ref array.DangerousGetReference(); + return ref this.array.DangerousGetReference(); } /// @@ -131,7 +163,7 @@ public ref T DangerousGetReference() [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Dispose() { - ArrayPool.Shared.Return(array); + this.pool.Return(this.array); } /// From f4da5920131d3f1ab2de6f6dc66b09881d10f662 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 16 Jun 2020 17:42:41 +0200 Subject: [PATCH 06/16] Minor tweaks to ArrayPoolBufferWriter --- .../Buffers/ArrayPoolBufferWriter{T}.cs | 24 ++++--------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/ArrayPoolBufferWriter{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/ArrayPoolBufferWriter{T}.cs index 1f884d69ab2..e17c0c48a18 100644 --- a/Microsoft.Toolkit.HighPerformance/Buffers/ArrayPoolBufferWriter{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Buffers/ArrayPoolBufferWriter{T}.cs @@ -71,9 +71,7 @@ public ArrayPoolBufferWriter(ArrayPool pool) /// Initializes a new instance of the class. /// /// The minimum capacity with which to initialize the underlying buffer. - /// - /// Thrown when is not positive (i.e. less than or equal to 0). - /// + /// Thrown when is not valid. public ArrayPoolBufferWriter(int initialCapacity) : this(ArrayPool.Shared, initialCapacity) { @@ -84,19 +82,15 @@ public ArrayPoolBufferWriter(int initialCapacity) /// /// The instance to use. /// The minimum capacity with which to initialize the underlying buffer. - /// - /// Thrown when is not positive (i.e. less than or equal to 0). - /// + /// Thrown when is not valid. public ArrayPoolBufferWriter(ArrayPool pool, int initialCapacity) { - if (initialCapacity <= 0) - { - ThrowArgumentOutOfRangeExceptionForInitialCapacity(); - } - // Since we're using pooled arrays, we can rent the buffer with the // default size immediately, we don't need to use lazy initialization // to save unnecessary memory allocations in this case. + // Additionally, we don't need to manually throw the exception if + // the requested size is not valid, as that'll be thrown automatically + // by the array pool in use when we try to rent an array with that size. this.pool = pool; this.array = pool.Rent(initialCapacity); this.index = 0; @@ -314,14 +308,6 @@ public override string ToString() return $"Microsoft.Toolkit.HighPerformance.Buffers.ArrayPoolBufferWriter<{typeof(T)}>[{this.index}]"; } - /// - /// Throws an when the initial capacity is invalid. - /// - private static void ThrowArgumentOutOfRangeExceptionForInitialCapacity() - { - throw new ArgumentOutOfRangeException("initialCapacity", "The initial capacity must be a positive value"); - } - /// /// Throws an when the requested count is negative. /// From 1f6aed077cfee59e399613c92d13392b9c66f9e3 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 16 Jun 2020 17:55:59 +0200 Subject: [PATCH 07/16] Added tests for invalid allocation sizes --- .../Buffers/Test_ArrayPoolBufferWriter{T}.cs | 10 ++++++++++ .../Buffers/Test_MemoryOwner{T}.cs | 10 ++++++++++ .../Buffers/Test_SpanOwner{T}.cs | 11 +++++++++++ 3 files changed, 31 insertions(+) diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_ArrayPoolBufferWriter{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_ArrayPoolBufferWriter{T}.cs index a4943487474..669a7b34ee4 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_ArrayPoolBufferWriter{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_ArrayPoolBufferWriter{T}.cs @@ -54,6 +54,16 @@ public void Test_ArrayPoolBufferWriterOfT_AllocateAndGetMemoryAndSpan() Assert.ThrowsException(() => writer.Advance(1)); } + [TestCategory("ArrayPoolBufferWriterOfT")] + [TestMethod] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void Test_ArrayPoolBufferWriterOfT_InvalidRequestedSize() + { + var writer = new ArrayPoolBufferWriter(-1); + + Assert.Fail("You shouldn't be here"); + } + [TestCategory("ArrayPoolBufferWriterOfT")] [TestMethod] public void Test_ArrayPoolBufferWriterOfT_Clear() diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_MemoryOwner{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_MemoryOwner{T}.cs index 6be4bbfa754..5540f8898fa 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_MemoryOwner{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_MemoryOwner{T}.cs @@ -30,6 +30,16 @@ public void Test_MemoryOwnerOfT_AllocateAndGetMemoryAndSpan() Assert.IsTrue(buffer.Span.ToArray().All(i => i == 42)); } + [TestCategory("MemoryOwnerOfT")] + [TestMethod] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void Test_MemoryOwnerOfT_InvalidRequestedSize() + { + using var buffer = MemoryOwner.Allocate(-1); + + Assert.Fail("You shouldn't be here"); + } + [TestCategory("MemoryOwnerOfT")] [TestMethod] [ExpectedException(typeof(ObjectDisposedException))] diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_SpanOwner{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_SpanOwner{T}.cs index e39de1f7a79..db8862dc591 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_SpanOwner{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_SpanOwner{T}.cs @@ -2,6 +2,7 @@ // 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.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.Toolkit.HighPerformance.Buffers; @@ -27,6 +28,16 @@ public void Test_SpanOwnerOfT_AllocateAndGetMemoryAndSpan() Assert.IsTrue(buffer.Span.ToArray().All(i => i == 42)); } + [TestCategory("SpanOwnerOfT")] + [TestMethod] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void Test_SpanOwnerOfT_InvalidRequestedSize() + { + using var buffer = SpanOwner.Allocate(-1); + + Assert.Fail("You shouldn't be here"); + } + [TestCategory("HashCodeOfT")] [TestMethod] public void Test_SpanOwnerOfT_PooledBuffersAndClear() From ae589c77e4156ec6c52b9d380bd3fa6be0f7da26 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 16 Jun 2020 18:13:44 +0200 Subject: [PATCH 08/16] Added unit tests for custom array pool overloads --- .../Buffers/Test_ArrayPoolBufferWriter{T}.cs | 46 +++++++++++++++++++ .../Buffers/Test_MemoryOwner{T}.cs | 25 ++++++++++ .../Buffers/Test_SpanOwner{T}.cs | 22 +++++++++ .../Buffers/TrackingArrayPool{T}.cs | 39 ++++++++++++++++ ...UnitTests.HighPerformance.Shared.projitems | 1 + 5 files changed, 133 insertions(+) create mode 100644 UnitTests/UnitTests.HighPerformance.Shared/Buffers/TrackingArrayPool{T}.cs diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_ArrayPoolBufferWriter{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_ArrayPoolBufferWriter{T}.cs index 669a7b34ee4..44e80b121fa 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_ArrayPoolBufferWriter{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_ArrayPoolBufferWriter{T}.cs @@ -9,6 +9,7 @@ using Microsoft.Toolkit.HighPerformance.Buffers; using Microsoft.Toolkit.HighPerformance.Extensions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using UnitTests.HighPerformance.Shared.Buffers; namespace UnitTests.HighPerformance.Buffers { @@ -54,6 +55,51 @@ public void Test_ArrayPoolBufferWriterOfT_AllocateAndGetMemoryAndSpan() Assert.ThrowsException(() => writer.Advance(1)); } + [TestCategory("ArrayPoolBufferWriterOfT")] + [TestMethod] + public void Test_ArrayPoolBufferWriterOfT_AllocateFromCustomPoolAndGetMemoryAndSpan() + { + var pool = new TrackingArrayPool(); + + using (var writer = new ArrayPoolBufferWriter()) + { + Assert.AreEqual(pool.RentedArrays.Count, 1); + + Assert.AreEqual(writer.Capacity, 256); + Assert.AreEqual(writer.FreeCapacity, 256); + Assert.AreEqual(writer.WrittenCount, 0); + Assert.IsTrue(writer.WrittenMemory.IsEmpty); + Assert.IsTrue(writer.WrittenSpan.IsEmpty); + + Span span = writer.GetSpan(43); + + Assert.IsTrue(span.Length >= 43); + + writer.Advance(43); + + Assert.AreEqual(writer.Capacity, 256); + Assert.AreEqual(writer.FreeCapacity, 256 - 43); + Assert.AreEqual(writer.WrittenCount, 43); + Assert.AreEqual(writer.WrittenMemory.Length, 43); + Assert.AreEqual(writer.WrittenSpan.Length, 43); + + Assert.ThrowsException(() => writer.Advance(-1)); + Assert.ThrowsException(() => writer.GetMemory(-1)); + Assert.ThrowsException(() => writer.Advance(1024)); + + writer.Dispose(); + + Assert.ThrowsException(() => writer.WrittenMemory); + Assert.ThrowsException(() => writer.WrittenSpan.Length); + Assert.ThrowsException(() => writer.Capacity); + Assert.ThrowsException(() => writer.FreeCapacity); + Assert.ThrowsException(() => writer.Clear()); + Assert.ThrowsException(() => writer.Advance(1)); + } + + Assert.AreEqual(pool.RentedArrays.Count, 0); + } + [TestCategory("ArrayPoolBufferWriterOfT")] [TestMethod] [ExpectedException(typeof(ArgumentOutOfRangeException))] diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_MemoryOwner{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_MemoryOwner{T}.cs index 5540f8898fa..f4e82383356 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_MemoryOwner{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_MemoryOwner{T}.cs @@ -3,10 +3,12 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers; using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.Toolkit.HighPerformance.Buffers; using Microsoft.VisualStudio.TestTools.UnitTesting; +using UnitTests.HighPerformance.Shared.Buffers; namespace UnitTests.HighPerformance.Buffers { @@ -30,6 +32,29 @@ public void Test_MemoryOwnerOfT_AllocateAndGetMemoryAndSpan() Assert.IsTrue(buffer.Span.ToArray().All(i => i == 42)); } + [TestCategory("MemoryOwnerOfT")] + [TestMethod] + public void Test_MemoryOwnerOfT_AllocateFromCustomPoolAndGetMemoryAndSpan() + { + var pool = new TrackingArrayPool(); + + using (var buffer = MemoryOwner.Allocate(127, pool)) + { + Assert.AreEqual(pool.RentedArrays.Count, 1); + + Assert.IsTrue(buffer.Length == 127); + Assert.IsTrue(buffer.Memory.Length == 127); + Assert.IsTrue(buffer.Span.Length == 127); + + buffer.Span.Fill(42); + + Assert.IsTrue(buffer.Memory.Span.ToArray().All(i => i == 42)); + Assert.IsTrue(buffer.Span.ToArray().All(i => i == 42)); + } + + Assert.AreEqual(pool.RentedArrays.Count, 0); + } + [TestCategory("MemoryOwnerOfT")] [TestMethod] [ExpectedException(typeof(ArgumentOutOfRangeException))] diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_SpanOwner{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_SpanOwner{T}.cs index db8862dc591..4f64eee4904 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_SpanOwner{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_SpanOwner{T}.cs @@ -7,6 +7,7 @@ using System.Linq; using Microsoft.Toolkit.HighPerformance.Buffers; using Microsoft.VisualStudio.TestTools.UnitTesting; +using UnitTests.HighPerformance.Shared.Buffers; namespace UnitTests.HighPerformance.Buffers { @@ -28,6 +29,27 @@ public void Test_SpanOwnerOfT_AllocateAndGetMemoryAndSpan() Assert.IsTrue(buffer.Span.ToArray().All(i => i == 42)); } + [TestCategory("SpanOwnerOfT")] + [TestMethod] + public void Test_SpanOwnerOfT_AllocateFromCustomPoolAndGetMemoryAndSpan() + { + var pool = new TrackingArrayPool(); + + using (var buffer = SpanOwner.Allocate(127)) + { + Assert.AreEqual(pool.RentedArrays.Count, 1); + + Assert.IsTrue(buffer.Length == 127); + Assert.IsTrue(buffer.Span.Length == 127); + + buffer.Span.Fill(42); + + Assert.IsTrue(buffer.Span.ToArray().All(i => i == 42)); + } + + Assert.AreEqual(pool.RentedArrays.Count, 0); + } + [TestCategory("SpanOwnerOfT")] [TestMethod] [ExpectedException(typeof(ArgumentOutOfRangeException))] diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Buffers/TrackingArrayPool{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Buffers/TrackingArrayPool{T}.cs new file mode 100644 index 00000000000..1ffedcefa0a --- /dev/null +++ b/UnitTests/UnitTests.HighPerformance.Shared/Buffers/TrackingArrayPool{T}.cs @@ -0,0 +1,39 @@ +// 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.Buffers; +using System.Collections.Generic; + +namespace UnitTests.HighPerformance.Shared.Buffers +{ + public sealed class TrackingArrayPool : ArrayPool + { + private readonly ArrayPool pool = ArrayPool.Create(); + + private readonly HashSet arrays = new HashSet(); + + /// + /// Gets the collection of currently rented out arrays + /// + public IReadOnlyCollection RentedArrays => this.arrays; + + /// + public override T[] Rent(int minimumLength) + { + T[] array = this.pool.Rent(minimumLength); + + this.arrays.Add(array); + + return array; + } + + /// + public override void Return(T[] array, bool clearArray = false) + { + this.arrays.Remove(array); + + this.pool.Return(array, clearArray); + } + } +} diff --git a/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems b/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems index 06b7503c5f8..6bfb905550b 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems +++ b/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems @@ -13,6 +13,7 @@ + From 7e0d20aac30e822704ef994016066d2f98b1adf4 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 23 Jun 2020 13:18:10 +0200 Subject: [PATCH 09/16] Fixed bugs in two unit tests --- .../Buffers/Test_ArrayPoolBufferWriter{T}.cs | 2 +- .../Buffers/Test_SpanOwner{T}.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_ArrayPoolBufferWriter{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_ArrayPoolBufferWriter{T}.cs index 44e80b121fa..e6770e01c59 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_ArrayPoolBufferWriter{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_ArrayPoolBufferWriter{T}.cs @@ -61,7 +61,7 @@ public void Test_ArrayPoolBufferWriterOfT_AllocateFromCustomPoolAndGetMemoryAndS { var pool = new TrackingArrayPool(); - using (var writer = new ArrayPoolBufferWriter()) + using (var writer = new ArrayPoolBufferWriter(pool)) { Assert.AreEqual(pool.RentedArrays.Count, 1); diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_SpanOwner{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_SpanOwner{T}.cs index 4f64eee4904..a79d5bb041d 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_SpanOwner{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_SpanOwner{T}.cs @@ -35,7 +35,7 @@ public void Test_SpanOwnerOfT_AllocateFromCustomPoolAndGetMemoryAndSpan() { var pool = new TrackingArrayPool(); - using (var buffer = SpanOwner.Allocate(127)) + using (var buffer = SpanOwner.Allocate(127, pool)) { Assert.AreEqual(pool.RentedArrays.Count, 1); From f31c2c7d87cb2acf68ab8a67019174c75e5c63ac Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 21 Jul 2020 13:25:28 +0200 Subject: [PATCH 10/16] Minor code style tweaks --- .../Buffers/MemoryBufferWriter{T}.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/MemoryBufferWriter{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/MemoryBufferWriter{T}.cs index b67b578e4ed..9a7d513fe33 100644 --- a/Microsoft.Toolkit.HighPerformance/Buffers/MemoryBufferWriter{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Buffers/MemoryBufferWriter{T}.cs @@ -106,7 +106,7 @@ public void Advance(int count) /// public Memory GetMemory(int sizeHint = 0) { - this.ValidateSizeHint(sizeHint); + ValidateSizeHint(sizeHint); return this.memory.Slice(this.index); } @@ -114,7 +114,7 @@ public Memory GetMemory(int sizeHint = 0) /// public Span GetSpan(int sizeHint = 0) { - this.ValidateSizeHint(sizeHint); + ValidateSizeHint(sizeHint); return this.memory.Slice(this.index).Span; } From ae961dd760e715cb7f9991c6b395c990d4f69384 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 21 Jul 2020 13:27:20 +0200 Subject: [PATCH 11/16] Minor codegen improvements --- .../Extensions/IBufferWriterExtensions.cs | 1 - .../Extensions/ReadOnlySpanExtensions.cs | 2 +- Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs | 1 - .../Extensions/StreamExtensions.cs | 1 - 4 files changed, 1 insertion(+), 4 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/IBufferWriterExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/IBufferWriterExtensions.cs index 09a8601e484..67c30579beb 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/IBufferWriterExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/IBufferWriterExtensions.cs @@ -102,7 +102,6 @@ public static void Write(this IBufferWriter writer, ReadOnlySpan span) /// /// Throws an when trying to write too many bytes to the target writer. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentExceptionForEndOfBuffer() { throw new ArgumentException("The current buffer writer can't contain the requested input data."); diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs index 4d3f2ad9c93..855da24a561 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs @@ -164,7 +164,7 @@ public static unsafe int IndexOf(this ReadOnlySpan span, in T value) /// The value to look for. /// The number of occurrences of in . [Pure] - [MethodImpl(MethodImplOptions.NoInlining)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int Count(this ReadOnlySpan span, T value) where T : IEquatable { diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs index bb2550257ba..8ce1cca75a0 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs @@ -223,7 +223,6 @@ public static int GetDjb2HashCode(this Span span) /// /// Throws an when the given reference is out of range. /// - [MethodImpl(MethodImplOptions.NoInlining)] internal static void ThrowArgumentOutOfRangeExceptionForInvalidReference() { throw new ArgumentOutOfRangeException("value", "The input reference does not belong to an element of the input span"); diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/StreamExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/StreamExtensions.cs index 25de163cc51..331786d086d 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/StreamExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/StreamExtensions.cs @@ -245,7 +245,6 @@ public static void Write(this Stream stream, in T value) /// /// Throws an when fails. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowInvalidOperationExceptionForEndOfStream() { throw new InvalidOperationException("The stream didn't contain enough data to read the requested item"); From 1438e3983291e009b845919a5c947b5b8c96c918 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 22 Jul 2020 00:16:20 +0200 Subject: [PATCH 12/16] Added more readonly modifiers to enumerators --- .../Enumerables/ReadOnlySpanEnumerable{T}.cs | 6 ++++-- .../Enumerables/ReadOnlySpanTokenizer{T}.cs | 6 ++++-- .../Enumerables/SpanEnumerable{T}.cs | 6 ++++-- .../Enumerables/SpanTokenizer{T}.cs | 6 ++++-- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanEnumerable{T}.cs index 29d42924536..c732861d670 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanEnumerable{T}.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -42,8 +43,9 @@ public ReadOnlySpanEnumerable(ReadOnlySpan span) /// Implements the duck-typed method. /// /// An instance targeting the current value. + [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpanEnumerable GetEnumerator() => this; + public readonly ReadOnlySpanEnumerable GetEnumerator() => this; /// /// Implements the duck-typed method. @@ -67,7 +69,7 @@ public bool MoveNext() /// /// Gets the duck-typed property. /// - public Item Current + public readonly Item Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] get diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanTokenizer{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanTokenizer{T}.cs index 3977f5829f8..37bc6168f10 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanTokenizer{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanTokenizer{T}.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; namespace Microsoft.Toolkit.HighPerformance.Enumerables @@ -54,8 +55,9 @@ public ReadOnlySpanTokenizer(ReadOnlySpan span, T separator) /// Implements the duck-typed method. /// /// An instance targeting the current value. + [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpanTokenizer GetEnumerator() => this; + public readonly ReadOnlySpanTokenizer GetEnumerator() => this; /// /// Implements the duck-typed method. @@ -94,7 +96,7 @@ public bool MoveNext() /// /// Gets the duck-typed property. /// - public ReadOnlySpan Current + public readonly ReadOnlySpan Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => this.span.Slice(this.start, this.end - this.start); diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/SpanEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/SpanEnumerable{T}.cs index 791f509339d..3648a5e5140 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/SpanEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/SpanEnumerable{T}.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -42,8 +43,9 @@ public SpanEnumerable(Span span) /// Implements the duck-typed method. /// /// An instance targeting the current value. + [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public SpanEnumerable GetEnumerator() => this; + public readonly SpanEnumerable GetEnumerator() => this; /// /// Implements the duck-typed method. @@ -67,7 +69,7 @@ public bool MoveNext() /// /// Gets the duck-typed property. /// - public Item Current + public readonly Item Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] get diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/SpanTokenizer{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/SpanTokenizer{T}.cs index 953eb535f0b..da8a9dce040 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/SpanTokenizer{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/SpanTokenizer{T}.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; namespace Microsoft.Toolkit.HighPerformance.Enumerables @@ -54,8 +55,9 @@ public SpanTokenizer(Span span, T separator) /// Implements the duck-typed method. /// /// An instance targeting the current value. + [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public SpanTokenizer GetEnumerator() => this; + public readonly SpanTokenizer GetEnumerator() => this; /// /// Implements the duck-typed method. @@ -94,7 +96,7 @@ public bool MoveNext() /// /// Gets the duck-typed property. /// - public Span Current + public readonly Span Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => this.span.Slice(this.start, this.end - this.start); From 580eda54ca88389ae9240467af37726143775cd0 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 7 Aug 2020 15:45:04 +0200 Subject: [PATCH 13/16] Improved a comment, fixed a typo --- .../Enumerables/SpanEnumerable{T}.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/SpanEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/SpanEnumerable{T}.cs index 3648a5e5140..23cbffac668 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/SpanEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/SpanEnumerable{T}.cs @@ -78,12 +78,12 @@ public readonly Item Current ref T r0 = ref MemoryMarshal.GetReference(this.span); ref T ri = ref Unsafe.Add(ref r0, this.index); - // On .NET Standard 2.1 we can save 4 bytes by piggybacking - // the current index in the length of the wrapped span. - // We're going to use the first item as the target reference, - // and the length as a host for the current original offset. - // This is not possible on .NET Standard 2.1 as we lack - // the API to create spans from arbitrary references. + // On .NET Standard 2.1 and .NET Core (or on any target that offers runtime + // support for the Span types), we can save 4 bytes by piggybacking the + // current index in the length of the wrapped span. We're going to use the + // first item as the target reference, and the length as a host for the + // current original offset. This is not possible on eg. .NET Standard 2.0, + // as we lack the API to create Span-s from arbitrary references. return new Item(ref ri, this.index); #else return new Item(this.span, this.index); From ca46e0cca960a1ccff340c84c1b3447ba1cb4ceb Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 17 Sep 2020 14:34:07 +0200 Subject: [PATCH 14/16] Added #ifdef block for unneeded using directive --- .../Extensions/StringExtensions.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/StringExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/StringExtensions.cs index 2d96413baa4..1fb0767fba1 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/StringExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/StringExtensions.cs @@ -5,7 +5,9 @@ using System; using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; +#if !NETCOREAPP3_1 using System.Runtime.InteropServices; +#endif using Microsoft.Toolkit.HighPerformance.Enumerables; using Microsoft.Toolkit.HighPerformance.Helpers.Internals; From 0c797645f160d907e4e8bd4d12d957ec50fb42e9 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 17 Sep 2020 15:51:59 +0200 Subject: [PATCH 15/16] Fixed incorrect XML docs --- .../Extensions/StringExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/StringExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/StringExtensions.cs index 1fb0767fba1..74ba2edd2fd 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/StringExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/StringExtensions.cs @@ -109,7 +109,7 @@ public static int Count(this string text, char c) /// { /// // Access the index and value of each item here... /// int index = item.Index; - /// string value = item.Value; + /// char value = item.Value; /// } /// /// The compiler will take care of properly setting up the loop with the type returned from this method. From 49b1ebaa7b4c46d024795089aa6e2ffe68e9aa91 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 18 Sep 2020 23:48:31 +0200 Subject: [PATCH 16/16] Switched a Span.CopyTo to Array.Copy --- .../Extensions/ArrayPoolExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayPoolExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayPoolExtensions.cs index 13d15a60aaf..1f7341a93e6 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayPoolExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayPoolExtensions.cs @@ -46,7 +46,7 @@ public static void Resize(this ArrayPool pool, ref T[]? array, int newSize T[] newArray = pool.Rent(newSize); int itemsToCopy = Math.Min(array.Length, newSize); - array.AsSpan(0, itemsToCopy).CopyTo(newArray); + Array.Copy(array, 0, newArray, 0, itemsToCopy); pool.Return(array, clearArray);