Skip to content

Commit c69b317

Browse files
committed
Optimize EnsureCapacity
1 parent a02c43d commit c69b317

File tree

7 files changed

+54
-55
lines changed

7 files changed

+54
-55
lines changed

src/LinkDotNet.StringBuilder/IntegerSpanList.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,15 @@ public void Add(int value)
3131
{
3232
if (count >= buffer.Length)
3333
{
34-
Grow();
34+
EnsureCapacity();
3535
}
3636

3737
buffer[count] = value;
3838
count++;
3939
}
4040

4141
[MethodImpl(MethodImplOptions.AggressiveInlining)]
42-
private void Grow(int capacity = 0)
42+
private void EnsureCapacity(int capacity = 0)
4343
{
4444
var currentSize = buffer.Length;
4545
var newSize = capacity > 0 ? capacity : currentSize * 2;

src/LinkDotNet.StringBuilder/ValueStringBuilder.Append.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public unsafe void Append(bool value)
2020

2121
if (newSize > buffer.Length)
2222
{
23-
Grow(newSize);
23+
EnsureCapacity(newSize);
2424
}
2525

2626
fixed (char* dest = &buffer[bufferPosition])
@@ -67,7 +67,7 @@ public void Append(scoped ReadOnlySpan<char> str)
6767
var newSize = str.Length + bufferPosition;
6868
if (newSize > buffer.Length)
6969
{
70-
Grow(newSize);
70+
EnsureCapacity(newSize);
7171
}
7272

7373
ref var strRef = ref MemoryMarshal.GetReference(str);
@@ -111,7 +111,7 @@ public void Append(char value)
111111
var newSize = bufferPosition + 1;
112112
if (newSize > buffer.Length)
113113
{
114-
Grow(newSize);
114+
EnsureCapacity(newSize);
115115
}
116116

117117
buffer[bufferPosition] = value;
@@ -163,7 +163,7 @@ public Span<char> AppendSpan(int length)
163163
var origPos = bufferPosition;
164164
if (origPos > buffer.Length - length)
165165
{
166-
Grow(length);
166+
EnsureCapacity(length);
167167
}
168168

169169
bufferPosition = origPos + length;
@@ -177,7 +177,7 @@ private void AppendSpanFormattable<T>(T value, ReadOnlySpan<char> format = defau
177177
var newSize = bufferSize + bufferPosition;
178178
if (newSize >= Capacity)
179179
{
180-
Grow(newSize);
180+
EnsureCapacity(newSize);
181181
}
182182

183183
if (!value.TryFormat(buffer[bufferPosition..], out var written, format, null))
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace LinkDotNet.StringBuilder;
2+
3+
public ref partial struct ValueStringBuilder
4+
{
5+
/// <summary>
6+
/// Finds the smallest power of 2 which is greater than or equal to <paramref name="minimum"/>.
7+
/// </summary>
8+
/// <param name="minimum">The value the result should be greater than or equal to.</param>
9+
/// <returns>The smallest power of 2 >= <paramref name="minimum"/>.</returns>
10+
internal static int FindSmallestPowerOf2Above(int minimum)
11+
{
12+
return 1 << (int)Math.Ceiling(Math.Log2(minimum));
13+
}
14+
}

src/LinkDotNet.StringBuilder/ValueStringBuilder.Insert.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public void Insert(int index, scoped ReadOnlySpan<char> value)
4545
var newLength = bufferPosition + value.Length;
4646
if (newLength > buffer.Length)
4747
{
48-
Grow(newLength);
48+
EnsureCapacity(newLength);
4949
}
5050

5151
bufferPosition = newLength;
@@ -79,7 +79,7 @@ private void InsertSpanFormattable<T>(int index, T value, scoped ReadOnlySpan<ch
7979
var newLength = bufferPosition + written;
8080
if (newLength > buffer.Length)
8181
{
82-
Grow(newLength);
82+
EnsureCapacity(newLength);
8383
}
8484

8585
bufferPosition = newLength;

src/LinkDotNet.StringBuilder/ValueStringBuilder.cs

Lines changed: 29 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,12 @@ namespace LinkDotNet.StringBuilder;
2020
private char[]? arrayFromPool;
2121

2222
/// <summary>
23-
/// Initializes a new instance of the <see cref="ValueStringBuilder"/> struct.
23+
/// Initializes a new instance of the <see cref="ValueStringBuilder"/> struct using a rented buffer of capacity 32.
2424
/// </summary>
2525
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2626
public ValueStringBuilder()
2727
{
28-
bufferPosition = 0;
29-
buffer = default;
30-
arrayFromPool = null;
31-
Grow(32);
28+
EnsureCapacity(32);
3229
}
3330

3431
/// <summary>
@@ -41,9 +38,7 @@ public ValueStringBuilder()
4138
#endif
4239
public ValueStringBuilder(Span<char> initialBuffer)
4340
{
44-
bufferPosition = 0;
4541
buffer = initialBuffer;
46-
arrayFromPool = null;
4742
}
4843

4944
/// <summary>
@@ -63,7 +58,7 @@ public ValueStringBuilder(ReadOnlySpan<char> initialText)
6358
[MethodImpl(MethodImplOptions.AggressiveInlining)]
6459
public ValueStringBuilder(int initialCapacity)
6560
{
66-
Grow(initialCapacity);
61+
EnsureCapacity(initialCapacity);
6762
}
6863

6964
/// <summary>
@@ -173,19 +168,41 @@ public readonly string ToString(Range range)
173168
public void Clear() => bufferPosition = 0;
174169

175170
/// <summary>
176-
/// Ensures that the builder has at least <paramref name="newCapacity"/> amount of capacity.
171+
/// Ensures the builder's buffer size is at least <paramref name="newCapacity"/>, renting a larger buffer if not.
177172
/// </summary>
178173
/// <param name="newCapacity">New capacity for the builder.</param>
179174
/// <remarks>
180-
/// If <paramref name="newCapacity"/> is smaller or equal to <see cref="Length"/> nothing will be done.
175+
/// If <see cref="Length"/> is already &gt;= <paramref name="newCapacity"/>, nothing is done.
181176
/// </remarks>
182177
[MethodImpl(MethodImplOptions.AggressiveInlining)]
183178
public void EnsureCapacity(int newCapacity)
184179
{
185-
if (newCapacity > Length)
180+
if (Length >= newCapacity)
186181
{
187-
Grow(newCapacity);
182+
return;
188183
}
184+
185+
int newSize = FindSmallestPowerOf2Above(newCapacity);
186+
187+
Span<char> rented = ArrayPool<char>.Shared.Rent(newSize);
188+
189+
if (bufferPosition > 0)
190+
{
191+
ref char sourceRef = ref MemoryMarshal.GetReference(buffer);
192+
ref char destinationRef = ref MemoryMarshal.GetReference(rented);
193+
194+
Unsafe.CopyBlock(
195+
ref Unsafe.As<char, byte>(ref destinationRef),
196+
ref Unsafe.As<char, byte>(ref sourceRef),
197+
(uint)bufferPosition * sizeof(char));
198+
}
199+
200+
if (arrayFromPool is not null)
201+
{
202+
ArrayPool<char>.Shared.Return(arrayFromPool);
203+
}
204+
205+
buffer = rented;
189206
}
190207

191208
/// <summary>
@@ -301,36 +318,4 @@ public void Dispose()
301318
/// </summary>
302319
[MethodImpl(MethodImplOptions.AggressiveInlining)]
303320
public readonly void Reverse() => buffer[..bufferPosition].Reverse();
304-
305-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
306-
private void Grow(int capacity = 0)
307-
{
308-
var size = buffer.Length == 0 ? 8 : buffer.Length;
309-
310-
while (size < capacity)
311-
{
312-
size *= 2;
313-
}
314-
315-
var rented = ArrayPool<char>.Shared.Rent(size);
316-
317-
if (bufferPosition > 0)
318-
{
319-
ref var sourceRef = ref MemoryMarshal.GetReference(buffer);
320-
ref var destinationRef = ref MemoryMarshal.GetReference(rented.AsSpan());
321-
322-
Unsafe.CopyBlock(
323-
ref Unsafe.As<char, byte>(ref destinationRef),
324-
ref Unsafe.As<char, byte>(ref sourceRef),
325-
(uint)(bufferPosition * sizeof(char)));
326-
}
327-
328-
var oldBufferFromPool = arrayFromPool;
329-
buffer = arrayFromPool = rented;
330-
331-
if (oldBufferFromPool is not null)
332-
{
333-
ArrayPool<char>.Shared.Return(oldBufferFromPool);
334-
}
335-
}
336321
}

src/LinkDotNet.StringBuilder/ValueStringBuilderExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public static System.Text.StringBuilder ToStringBuilder(this ValueStringBuilder
2525
/// <param name="builder">The builder from which the new instance is derived.</param>
2626
/// <returns>A new <see cref="ValueStringBuilder"/> instance with the string represented by this builder.</returns>
2727
/// <exception cref="ArgumentNullException">Throws if <paramref name="builder"/> is null.</exception>
28-
public static ValueStringBuilder ToValueStringBuilder(this System.Text.StringBuilder? builder)
28+
public static ValueStringBuilder ToValueStringBuilder(this System.Text.StringBuilder builder)
2929
{
3030
ArgumentNullException.ThrowIfNull(builder);
3131

tests/LinkDotNet.StringBuilder.UnitTests/ValueStringBuilderExtensionsTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public void ShouldThrowWhenStringBuilderNull()
3131
{
3232
System.Text.StringBuilder? sb = null;
3333

34-
Action act = () => sb.ToValueStringBuilder();
34+
Action act = () => sb!.ToValueStringBuilder();
3535

3636
act.ShouldThrow<ArgumentNullException>();
3737
}

0 commit comments

Comments
 (0)