Skip to content

Commit c11704a

Browse files
committed
Proposal of stack based array builders, either fixed size and/or growing
1 parent 1d0e755 commit c11704a

File tree

3 files changed

+199
-0
lines changed

3 files changed

+199
-0
lines changed
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
using System.Diagnostics;
2+
using System.Runtime.CompilerServices;
3+
4+
namespace StackArrayBuilder;
5+
6+
/// <summary>
7+
/// Helper type for avoiding allocations while building arrays.
8+
/// </summary>
9+
/// <typeparam name="T">The element type.</typeparam>
10+
/// <remarks>
11+
/// Will grow heap allocated size, if you need it.
12+
/// Only use grow in rare cases, as it needs to grow the array, if over already allocated size.
13+
/// If you are certain of the max size needed, you can use e.g. <code>StackArrayBuilder8</code>
14+
/// </remarks>
15+
public ref struct StackArrayBuilder<T>
16+
{
17+
private InlineArray16<T> _stackAllocatedBuffer = default;
18+
public const int StackAllocatedCapacity = 16;
19+
private const int DefaultHeapCapacity = 4;
20+
21+
private T[]? _heapArrayBuffer; // Starts out null, initialized if capacity is over stack allocated size when constructing or on Add.
22+
private int _count; // Number of items added.
23+
24+
/// <summary>
25+
/// Initializes the <see cref="StackArrayBuilder{T}"/> with a specified capacity.
26+
/// </summary>
27+
/// <param name="capacity">The capacity of the array to allocate.</param>
28+
public StackArrayBuilder(int capacity) : this()
29+
{
30+
Debug.Assert(capacity >= 0);
31+
if (capacity > StackAllocatedCapacity)
32+
{
33+
_heapArrayBuffer = new T[capacity - StackAllocatedCapacity];
34+
}
35+
}
36+
37+
/// <summary>
38+
/// Gets the number of items this instance can store without re-allocating.
39+
/// <c>StackAllocatedCapacity</c> if the backing heap array is not needed, all up to that is already stack allocated
40+
/// </summary>
41+
/// <remarks>Only for unit testing, checking that overallocation does not happen</remarks>
42+
public int Capacity => _heapArrayBuffer?.Length + StackAllocatedCapacity ?? StackAllocatedCapacity;
43+
44+
/// <summary>
45+
/// Adds an item, resizing heap allocated array if necessary.
46+
/// </summary>
47+
/// <param name="item">The item to add.</param>
48+
public void Add(T item)
49+
{
50+
if (_count == Capacity)
51+
{
52+
EnsureCapacity(_count + 1);
53+
}
54+
55+
UncheckedAdd(item);
56+
}
57+
58+
/// <summary>
59+
/// Creates an array from the contents of this builder.
60+
/// </summary>
61+
public T[] ToArray()
62+
{
63+
if (_count == 0)
64+
{
65+
return [];
66+
}
67+
68+
T[] result = new T[_count];
69+
int index = 0;
70+
foreach (T stackAllocatedValue in _stackAllocatedBuffer)
71+
{
72+
result[index++] = stackAllocatedValue;
73+
if (index >= _count)
74+
{
75+
return result;
76+
}
77+
}
78+
79+
_heapArrayBuffer.AsSpan(0, _count - StackAllocatedCapacity).CopyTo(result.AsSpan(start: StackAllocatedCapacity));
80+
81+
return result;
82+
}
83+
84+
/// <summary>
85+
/// Adds an item, without checking if there is room.
86+
/// </summary>
87+
/// <param name="item">The item to add.</param>
88+
/// <remarks>
89+
/// Use this method if you know there is enough space in the <see cref="StackArrayBuilder{T}"/>
90+
/// for another item, and you are writing performance-sensitive code.
91+
/// </remarks>
92+
public void UncheckedAdd(T item)
93+
{
94+
Debug.Assert(_count < Capacity);
95+
if (_count < StackAllocatedCapacity)
96+
{
97+
_stackAllocatedBuffer[_count++] = item;
98+
}
99+
else
100+
{
101+
_heapArrayBuffer![_count++ - StackAllocatedCapacity] = item;
102+
}
103+
}
104+
105+
private void EnsureCapacity(int minimum)
106+
{
107+
Debug.Assert(minimum > Capacity);
108+
109+
if (minimum < StackAllocatedCapacity)
110+
{
111+
return; // There is still room on the stack
112+
}
113+
114+
if (_heapArrayBuffer == null)
115+
{
116+
// Initial capacity has not been not set or too low, we will allocate the default heap array size
117+
_heapArrayBuffer = new T[DefaultHeapCapacity];
118+
return;
119+
}
120+
121+
// Check if allocated heap capacity was enough
122+
int defaultCapacityWithHeap = _heapArrayBuffer.Length + StackAllocatedCapacity;
123+
if (defaultCapacityWithHeap >= minimum)
124+
{
125+
return; // current allocated stack+heap is large enough
126+
}
127+
128+
// We need to allocate more heap capacity, by increasing the size of the array
129+
int nextHeapCapacity = 2 * _heapArrayBuffer.Length;
130+
131+
if ((uint)nextHeapCapacity > (uint)Array.MaxLength)
132+
{
133+
nextHeapCapacity = Math.Max(_heapArrayBuffer.Length + 1, Array.MaxLength);
134+
}
135+
136+
nextHeapCapacity = Math.Max(nextHeapCapacity, minimum);
137+
138+
Array.Resize(ref _heapArrayBuffer, nextHeapCapacity);
139+
}
140+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System.Runtime.CompilerServices;
2+
3+
namespace StackArrayBuilder;
4+
5+
/// <summary>
6+
/// Helper type for avoiding allocations while building arrays.
7+
/// </summary>
8+
/// <typeparam name="T">The element type.</typeparam>
9+
/// <remarks>
10+
/// Throws <code>InvalidOperationException</code>, if the right size is not selected.
11+
/// Be sure to select the right size to not over- or under-allocate expected size on stack.
12+
/// </remarks>
13+
public ref struct StackArrayBuilder8<T>
14+
{
15+
private InlineArray8<T> _stackAllocatedBuffer = default;
16+
public const int StackAllocatedCapacity = 8;
17+
18+
private int _count = 0; // Number of items added.
19+
20+
public StackArrayBuilder8()
21+
{
22+
}
23+
24+
/// <summary>Adds an item</summary>
25+
/// <param name="item">The item to add.</param>
26+
public void Add(T item)
27+
{
28+
if (_count == StackAllocatedCapacity)
29+
{
30+
throw new InvalidOperationException("Stack allocated capacity exceeded");
31+
}
32+
_stackAllocatedBuffer[_count++] = item;
33+
}
34+
35+
/// <summary>Creates an array from the contents of this builder.</summary>
36+
public T[] ToArray()
37+
{
38+
if (_count == 0)
39+
{
40+
return [];
41+
}
42+
43+
T[] result = new T[_count];
44+
int index = 0;
45+
foreach (T stackAllocatedValue in _stackAllocatedBuffer)
46+
{
47+
result[index++] = stackAllocatedValue;
48+
if (index >= _count)
49+
{
50+
return result;
51+
}
52+
}
53+
return result;
54+
}
55+
}

src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@
4747
Link="Common\System\Text\ValueStringBuilder.cs" />
4848
<Compile Include="$(CommonPath)System\Collections\Generic\ArrayBuilder.cs"
4949
Link="Common\System\Collections\Generic\ArrayBuilder.cs" />
50+
<Compile Include="..\..\Common\src\System\Collections\Generic\StackArrayBuilder.cs"
51+
Link="Common\System\Collections\Generic\StackArrayBuilder.cs" />
52+
<Compile Include="..\..\Common\src\System\Collections\Generic\StackArrayBuilder8.cs"
53+
Link="Common\System\Collections\Generic\StackArrayBuilder8.cs" />
5054
</ItemGroup>
5155

5256
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'windows'">

0 commit comments

Comments
 (0)