Skip to content

Conversation

paulomorgado
Copy link
Contributor

🔧 Replace String Concatenation with ValueStringBuilder for Performance Optimization

Summary

This PR refactors string concatenation logic in the library to use a custom ValueStringBuilder implementation backed by ArrayPool<char>.Shared. This change improves performance by reducing memory allocations and GC pressure, especially in hot paths or high-throughput scenarios.


📌 Why Avoid String Concatenation?

String concatenation using the + operator or string.Concat creates a new string instance for each operation, leading to:

  • Excessive memory allocations: Each concatenation results in a new string allocation.
  • Increased GC pressure: Temporary strings are short-lived and quickly fill up Gen 0.
  • Poor performance in loops: Repeated concatenation in loops scales poorly.

✅ Why Use StringBuilder?

StringBuilder is a mutable buffer that allows efficient appending of strings without creating intermediate string instances. It is ideal for scenarios involving:

  • Multiple string manipulations
  • Dynamic string construction
  • Reducing memory churn

However, StringBuilder itself has limitations:

  • It allocates on the heap.
  • It may over-allocate internal buffers.
  • It incurs some overhead due to its object-oriented nature.

🚀 Why ValueStringBuilder with ArrayPool<char>.Shared?

To further optimize performance, this PR introduces a ValueStringBuilder that:

  • Uses stack allocation when possible (via Span<char>)
  • Falls back to pooled arrays from ArrayPool<char>.Shared for larger buffers
  • Avoids heap allocations in most cases
  • Minimizes GC pressure by reusing buffers

This approach is inspired by internal .NET implementations (e.g., System.Text.Json, Roslyn) and is particularly effective in performance-critical code paths.

🧩 Collaborative String Composition with ToString(ref ValueStringBuilder)

This implementation also introduces ToString(ref ValueStringBuilder) methods to enable collaborative string composition. Instead of each component returning a fully-formed string via ToString(), which would then be copied into a larger result, each component writes directly into a shared ValueStringBuilder.

Benefits:

  • Zero intermediate allocations: No temporary strings are created.
  • Single-pass construction: The final string is built in one go.
  • Improved performance: Avoids redundant copying and reduces GC pressure.

Example:

public void ToString(ref ValueStringBuilder sb)
{
    sb.Append("Item: ");
    sb.Append(Name);
    sb.Append(", Value: ");
    sb.Append(Value);
}

And in the parent object:

public override string ToString()
{
    var sb = new ValueStringBuilder(stackalloc char[256]);
    try
    {
        foreach (var item in Items)
        {
            item.ToString(ref sb);
        }
        return sb.ToString();
    }
    finally
    {
        sb.Dispose();
    }
}

NOTE: This does not replace all occurrences of string concatenation and StringBuilder, but does most of it.

@paulomorgado paulomorgado marked this pull request as ready for review May 30, 2025 19:40
- Added `Microsoft.Extensions.ObjectPool` package for improved object pooling.
- Introduced `Microsoft.NETFramework.ReferenceAssemblies.net462` for `net462` target framework.
- Updated project warnings and added `AllowUnsafeBlocks` property for specific frameworks.
- Refactored multiple `ToString` methods and other string handling to use `ValueStringBuilder`, reducing allocations and improving performance.
- Updated `SIPAuthorisationDigest` and `SIPConstants` for better memory management using `AsSpan()` and `ValueStringBuilder`.
- Enhanced `CRC32` class and JSON serialization in `JSONWriter` to utilize span-based operations for efficiency.
- Updated unit tests to reflect changes in logging and data handling.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants