diff --git a/src/libraries/Common/src/System/Collections/Generic/StackArrayBuilder.cs b/src/libraries/Common/src/System/Collections/Generic/StackArrayBuilder.cs new file mode 100644 index 00000000000000..4c202b67641f8d --- /dev/null +++ b/src/libraries/Common/src/System/Collections/Generic/StackArrayBuilder.cs @@ -0,0 +1,140 @@ +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace StackArrayBuilder; + +/// +/// Helper type for avoiding allocations while building arrays. +/// +/// The element type. +/// +/// Will grow heap allocated size, if you need it. +/// Only use grow in rare cases, as it needs to grow the array, if over already allocated size. +/// If you are certain of the max size needed, you can use e.g. StackArrayBuilder8 +/// +internal ref struct StackArrayBuilder +{ + private InlineArray16 _stackAllocatedBuffer = default; + public const int StackAllocatedCapacity = 16; + private const int DefaultHeapCapacity = 4; + + private T[]? _heapArrayBuffer; // Starts out null, initialized if capacity is over stack allocated size when constructing or on Add. + private int _count; // Number of items added. + + /// + /// Initializes the with a specified capacity. + /// + /// The capacity of the array to allocate. + public StackArrayBuilder(int capacity) : this() + { + Debug.Assert(capacity >= 0); + if (capacity > StackAllocatedCapacity) + { + _heapArrayBuffer = new T[capacity - StackAllocatedCapacity]; + } + } + + /// + /// Gets the number of items this instance can store without re-allocating. + /// StackAllocatedCapacity if the backing heap array is not needed, all up to that is already stack allocated + /// + /// Only for unit testing, checking that overallocation does not happen + public int Capacity => _heapArrayBuffer?.Length + StackAllocatedCapacity ?? StackAllocatedCapacity; + + /// + /// Adds an item, resizing heap allocated array if necessary. + /// + /// The item to add. + public void Add(T item) + { + if (_count == Capacity) + { + EnsureCapacity(_count + 1); + } + + UncheckedAdd(item); + } + + /// + /// Creates an array from the contents of this builder. + /// + public T[] ToArray() + { + if (_count == 0) + { + return []; + } + + T[] result = new T[_count]; + int index = 0; + foreach (T stackAllocatedValue in _stackAllocatedBuffer) + { + result[index++] = stackAllocatedValue; + if (index >= _count) + { + return result; + } + } + + _heapArrayBuffer.AsSpan(0, _count - StackAllocatedCapacity).CopyTo(result.AsSpan(start: StackAllocatedCapacity)); + + return result; + } + + /// + /// Adds an item, without checking if there is room. + /// + /// The item to add. + /// + /// Use this method if you know there is enough space in the + /// for another item, and you are writing performance-sensitive code. + /// + public void UncheckedAdd(T item) + { + Debug.Assert(_count < Capacity); + if (_count < StackAllocatedCapacity) + { + _stackAllocatedBuffer[_count++] = item; + } + else + { + _heapArrayBuffer![_count++ - StackAllocatedCapacity] = item; + } + } + + private void EnsureCapacity(int minimum) + { + Debug.Assert(minimum > Capacity); + + if (minimum < StackAllocatedCapacity) + { + return; // There is still room on the stack + } + + if (_heapArrayBuffer == null) + { + // Initial capacity has not been not set or too low, we will allocate the default heap array size + _heapArrayBuffer = new T[DefaultHeapCapacity]; + return; + } + + // Check if allocated heap capacity was enough + int defaultCapacityWithHeap = _heapArrayBuffer.Length + StackAllocatedCapacity; + if (defaultCapacityWithHeap >= minimum) + { + return; // current allocated stack+heap is large enough + } + + // We need to allocate more heap capacity, by increasing the size of the array + int nextHeapCapacity = 2 * _heapArrayBuffer.Length; + + if ((uint)nextHeapCapacity > (uint)Array.MaxLength) + { + nextHeapCapacity = Math.Max(_heapArrayBuffer.Length + 1, Array.MaxLength); + } + + nextHeapCapacity = Math.Max(nextHeapCapacity, minimum); + + Array.Resize(ref _heapArrayBuffer, nextHeapCapacity); + } +} diff --git a/src/libraries/Common/src/System/Collections/Generic/StackArrayBuilder8.cs b/src/libraries/Common/src/System/Collections/Generic/StackArrayBuilder8.cs new file mode 100644 index 00000000000000..c17429e9f0f0b5 --- /dev/null +++ b/src/libraries/Common/src/System/Collections/Generic/StackArrayBuilder8.cs @@ -0,0 +1,55 @@ +using System.Runtime.CompilerServices; + +namespace StackArrayBuilder; + +/// +/// Helper type for avoiding allocations while building arrays. +/// +/// The element type. +/// +/// Throws InvalidOperationException, if the right size is not selected. +/// Be sure to select the right size to not over- or under-allocate expected size on stack. +/// +internal ref struct StackArrayBuilder8 +{ + private InlineArray8 _stackAllocatedBuffer = default; + public const int StackAllocatedCapacity = 8; + + private int _count = 0; // Number of items added. + + public StackArrayBuilder8() + { + } + + /// Adds an item + /// The item to add. + public void Add(T item) + { + if (_count == StackAllocatedCapacity) + { + throw new InvalidOperationException("Stack allocated capacity exceeded"); + } + _stackAllocatedBuffer[_count++] = item; + } + + /// Creates an array from the contents of this builder. + public T[] ToArray() + { + if (_count == 0) + { + return []; + } + + T[] result = new T[_count]; + int index = 0; + foreach (T stackAllocatedValue in _stackAllocatedBuffer) + { + result[index++] = stackAllocatedValue; + if (index >= _count) + { + return result; + } + } + return result; + } +} diff --git a/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj b/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj index ad692fafc90f2c..481c719722f9f0 100644 --- a/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj +++ b/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj @@ -47,6 +47,10 @@ Link="Common\System\Text\ValueStringBuilder.cs" /> + +