Skip to content

Commit

Permalink
Merge pull request #228 from Joy-less/optimize-ensure-capacity
Browse files Browse the repository at this point in the history
Optimize EnsureCapacity
  • Loading branch information
linkdotnet authored Jan 25, 2025
2 parents b68394a + 8bcec51 commit f3f4759
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 67 deletions.
4 changes: 2 additions & 2 deletions src/LinkDotNet.StringBuilder/IntegerSpanList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@ public void Add(int value)
{
if (count >= buffer.Length)
{
Grow();
EnsureCapacity();
}

buffer[count] = value;
count++;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Grow(int capacity = 0)
private void EnsureCapacity(int capacity = 0)
{
var currentSize = buffer.Length;
var newSize = capacity > 0 ? capacity : currentSize * 2;
Expand Down
10 changes: 5 additions & 5 deletions src/LinkDotNet.StringBuilder/ValueStringBuilder.Append.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public unsafe void Append(bool value)

if (newSize > buffer.Length)
{
Grow(newSize);
EnsureCapacity(newSize);
}

fixed (char* dest = &buffer[bufferPosition])
Expand Down Expand Up @@ -67,7 +67,7 @@ public void Append(scoped ReadOnlySpan<char> str)
var newSize = str.Length + bufferPosition;
if (newSize > buffer.Length)
{
Grow(newSize);
EnsureCapacity(newSize);
}

ref var strRef = ref MemoryMarshal.GetReference(str);
Expand Down Expand Up @@ -111,7 +111,7 @@ public void Append(char value)
var newSize = bufferPosition + 1;
if (newSize > buffer.Length)
{
Grow(newSize);
EnsureCapacity(newSize);
}

buffer[bufferPosition] = value;
Expand Down Expand Up @@ -163,7 +163,7 @@ public Span<char> AppendSpan(int length)
var origPos = bufferPosition;
if (origPos > buffer.Length - length)
{
Grow(length);
EnsureCapacity(length);
}

bufferPosition = origPos + length;
Expand All @@ -177,7 +177,7 @@ private void AppendSpanFormattable<T>(T value, ReadOnlySpan<char> format = defau
var newSize = bufferSize + bufferPosition;
if (newSize >= Capacity)
{
Grow(newSize);
EnsureCapacity(newSize);
}

if (!value.TryFormat(buffer[bufferPosition..], out var written, format, null))
Expand Down
57 changes: 57 additions & 0 deletions src/LinkDotNet.StringBuilder/ValueStringBuilder.EnsureCapacity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace LinkDotNet.StringBuilder;

public ref partial struct ValueStringBuilder
{
/// <summary>
/// Ensures the builder's buffer size is at least <paramref name="newCapacity"/>, renting a larger buffer if not.
/// </summary>
/// <param name="newCapacity">New capacity for the builder.</param>
/// <remarks>
/// If <see cref="Length"/> is already &gt;= <paramref name="newCapacity"/>, nothing is done.
/// </remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void EnsureCapacity(int newCapacity)
{
if (Length >= newCapacity)
{
return;
}

var newSize = FindSmallestPowerOf2Above(newCapacity);

var rented = ArrayPool<char>.Shared.Rent(newSize);

if (bufferPosition > 0)
{
ref var sourceRef = ref MemoryMarshal.GetReference(buffer);
ref var destinationRef = ref MemoryMarshal.GetReference(rented.AsSpan());

Unsafe.CopyBlock(
ref Unsafe.As<char, byte>(ref destinationRef),
ref Unsafe.As<char, byte>(ref sourceRef),
(uint)bufferPosition * sizeof(char));
}

if (arrayFromPool is not null)
{
ArrayPool<char>.Shared.Return(arrayFromPool);
}

buffer = rented;
arrayFromPool = rented;
}

/// <summary>
/// Finds the smallest power of 2 which is greater than or equal to <paramref name="minimum"/>.
/// </summary>
/// <param name="minimum">The value the result should be greater than or equal to.</param>
/// <returns>The smallest power of 2 >= <paramref name="minimum"/>.</returns>
private static int FindSmallestPowerOf2Above(int minimum)
{
return 1 << (int)Math.Ceiling(Math.Log2(minimum));
}
}
4 changes: 2 additions & 2 deletions src/LinkDotNet.StringBuilder/ValueStringBuilder.Insert.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public void Insert(int index, scoped ReadOnlySpan<char> value)
var newLength = bufferPosition + value.Length;
if (newLength > buffer.Length)
{
Grow(newLength);
EnsureCapacity(newLength);
}

bufferPosition = newLength;
Expand Down Expand Up @@ -103,7 +103,7 @@ private void InsertSpanFormattable<T>(int index, T value, scoped ReadOnlySpan<ch
var newLength = bufferPosition + written;
if (newLength > buffer.Length)
{
Grow(newLength);
EnsureCapacity(newLength);
}

bufferPosition = newLength;
Expand Down
59 changes: 3 additions & 56 deletions src/LinkDotNet.StringBuilder/ValueStringBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,12 @@ namespace LinkDotNet.StringBuilder;
private char[]? arrayFromPool;

/// <summary>
/// Initializes a new instance of the <see cref="ValueStringBuilder"/> struct.
/// Initializes a new instance of the <see cref="ValueStringBuilder"/> struct using a rented buffer of capacity 32.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ValueStringBuilder()
{
bufferPosition = 0;
buffer = default;
arrayFromPool = null;
Grow(32);
EnsureCapacity(32);
}

/// <summary>
Expand All @@ -41,9 +38,7 @@ public ValueStringBuilder()
#endif
public ValueStringBuilder(Span<char> initialBuffer)
{
bufferPosition = 0;
buffer = initialBuffer;
arrayFromPool = null;
}

/// <summary>
Expand All @@ -63,7 +58,7 @@ public ValueStringBuilder(ReadOnlySpan<char> initialText)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ValueStringBuilder(int initialCapacity)
{
Grow(initialCapacity);
EnsureCapacity(initialCapacity);
}

/// <summary>
Expand Down Expand Up @@ -172,22 +167,6 @@ public readonly string ToString(Range range)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Clear() => bufferPosition = 0;

/// <summary>
/// Ensures that the builder has at least <paramref name="newCapacity"/> amount of capacity.
/// </summary>
/// <param name="newCapacity">New capacity for the builder.</param>
/// <remarks>
/// If <paramref name="newCapacity"/> is smaller or equal to <see cref="Length"/> nothing will be done.
/// </remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void EnsureCapacity(int newCapacity)
{
if (newCapacity > Length)
{
Grow(newCapacity);
}
}

/// <summary>
/// Removes a range of characters from this builder.
/// </summary>
Expand Down Expand Up @@ -301,36 +280,4 @@ public void Dispose()
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void Reverse() => buffer[..bufferPosition].Reverse();

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Grow(int capacity = 0)
{
var size = buffer.Length == 0 ? 8 : buffer.Length;

while (size < capacity)
{
size *= 2;
}

var rented = ArrayPool<char>.Shared.Rent(size);

if (bufferPosition > 0)
{
ref var sourceRef = ref MemoryMarshal.GetReference(buffer);
ref var destinationRef = ref MemoryMarshal.GetReference(rented.AsSpan());

Unsafe.CopyBlock(
ref Unsafe.As<char, byte>(ref destinationRef),
ref Unsafe.As<char, byte>(ref sourceRef),
(uint)(bufferPosition * sizeof(char)));
}

var oldBufferFromPool = arrayFromPool;
buffer = arrayFromPool = rented;

if (oldBufferFromPool is not null)
{
ArrayPool<char>.Shared.Return(oldBufferFromPool);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public static System.Text.StringBuilder ToStringBuilder(this ValueStringBuilder
/// <param name="builder">The builder from which the new instance is derived.</param>
/// <returns>A new <see cref="ValueStringBuilder"/> instance with the string represented by this builder.</returns>
/// <exception cref="ArgumentNullException">Throws if <paramref name="builder"/> is null.</exception>
public static ValueStringBuilder ToValueStringBuilder(this System.Text.StringBuilder? builder)
public static ValueStringBuilder ToValueStringBuilder(this System.Text.StringBuilder builder)
{
ArgumentNullException.ThrowIfNull(builder);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public void ShouldThrowWhenStringBuilderNull()
{
System.Text.StringBuilder? sb = null;

Action act = () => sb.ToValueStringBuilder();
Action act = () => sb!.ToValueStringBuilder();

act.ShouldThrow<ArgumentNullException>();
}
Expand Down

0 comments on commit f3f4759

Please sign in to comment.