Skip to content

Commit f3f4759

Browse files
authored
Merge pull request #228 from Joy-less/optimize-ensure-capacity
Optimize EnsureCapacity
2 parents b68394a + 8bcec51 commit f3f4759

File tree

7 files changed

+71
-67
lines changed

7 files changed

+71
-67
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: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using System.Buffers;
2+
using System.Runtime.CompilerServices;
3+
using System.Runtime.InteropServices;
4+
5+
namespace LinkDotNet.StringBuilder;
6+
7+
public ref partial struct ValueStringBuilder
8+
{
9+
/// <summary>
10+
/// Ensures the builder's buffer size is at least <paramref name="newCapacity"/>, renting a larger buffer if not.
11+
/// </summary>
12+
/// <param name="newCapacity">New capacity for the builder.</param>
13+
/// <remarks>
14+
/// If <see cref="Length"/> is already &gt;= <paramref name="newCapacity"/>, nothing is done.
15+
/// </remarks>
16+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
17+
public void EnsureCapacity(int newCapacity)
18+
{
19+
if (Length >= newCapacity)
20+
{
21+
return;
22+
}
23+
24+
var newSize = FindSmallestPowerOf2Above(newCapacity);
25+
26+
var rented = ArrayPool<char>.Shared.Rent(newSize);
27+
28+
if (bufferPosition > 0)
29+
{
30+
ref var sourceRef = ref MemoryMarshal.GetReference(buffer);
31+
ref var destinationRef = ref MemoryMarshal.GetReference(rented.AsSpan());
32+
33+
Unsafe.CopyBlock(
34+
ref Unsafe.As<char, byte>(ref destinationRef),
35+
ref Unsafe.As<char, byte>(ref sourceRef),
36+
(uint)bufferPosition * sizeof(char));
37+
}
38+
39+
if (arrayFromPool is not null)
40+
{
41+
ArrayPool<char>.Shared.Return(arrayFromPool);
42+
}
43+
44+
buffer = rented;
45+
arrayFromPool = rented;
46+
}
47+
48+
/// <summary>
49+
/// Finds the smallest power of 2 which is greater than or equal to <paramref name="minimum"/>.
50+
/// </summary>
51+
/// <param name="minimum">The value the result should be greater than or equal to.</param>
52+
/// <returns>The smallest power of 2 >= <paramref name="minimum"/>.</returns>
53+
private static int FindSmallestPowerOf2Above(int minimum)
54+
{
55+
return 1 << (int)Math.Ceiling(Math.Log2(minimum));
56+
}
57+
}

src/LinkDotNet.StringBuilder/ValueStringBuilder.Insert.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public void Insert(int index, scoped ReadOnlySpan<char> value)
6969
var newLength = bufferPosition + value.Length;
7070
if (newLength > buffer.Length)
7171
{
72-
Grow(newLength);
72+
EnsureCapacity(newLength);
7373
}
7474

7575
bufferPosition = newLength;
@@ -103,7 +103,7 @@ private void InsertSpanFormattable<T>(int index, T value, scoped ReadOnlySpan<ch
103103
var newLength = bufferPosition + written;
104104
if (newLength > buffer.Length)
105105
{
106-
Grow(newLength);
106+
EnsureCapacity(newLength);
107107
}
108108

109109
bufferPosition = newLength;

src/LinkDotNet.StringBuilder/ValueStringBuilder.cs

Lines changed: 3 additions & 56 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>
@@ -172,22 +167,6 @@ public readonly string ToString(Range range)
172167
[MethodImpl(MethodImplOptions.AggressiveInlining)]
173168
public void Clear() => bufferPosition = 0;
174169

175-
/// <summary>
176-
/// Ensures that the builder has at least <paramref name="newCapacity"/> amount of capacity.
177-
/// </summary>
178-
/// <param name="newCapacity">New capacity for the builder.</param>
179-
/// <remarks>
180-
/// If <paramref name="newCapacity"/> is smaller or equal to <see cref="Length"/> nothing will be done.
181-
/// </remarks>
182-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
183-
public void EnsureCapacity(int newCapacity)
184-
{
185-
if (newCapacity > Length)
186-
{
187-
Grow(newCapacity);
188-
}
189-
}
190-
191170
/// <summary>
192171
/// Removes a range of characters from this builder.
193172
/// </summary>
@@ -301,36 +280,4 @@ public void Dispose()
301280
/// </summary>
302281
[MethodImpl(MethodImplOptions.AggressiveInlining)]
303282
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-
}
336283
}

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)