StringBuilder with and without initial capacity - .NET 8
Date Added (UTC):
05 Apr 2024 @ 04:34
Date Updated (UTC):05 Apr 2024 @ 04:34
.NET Version(s): Tag(s):
#StringBuilder #StringConcatenation
Added By:
.NET Developer and tech lead from Ireland!
Benchmark Results:
Benchmark Code:
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Reports;
using System.Text;
namespace Benchmarks
{
[MemoryDiagnoser]
[Config(typeof(Config))]
[SimpleJob(RuntimeMoniker.Net80)]
[HideColumns(Column.Job, Column.RatioSD, Column.AllocRatio)]
public class StringBuilderInitialCapacity
{
private class Config : ManualConfig
{
public Config()
{
SummaryStyle =
SummaryStyle.Default.WithRatioStyle(RatioStyle.Percentage);
}
}
private string
title = "Mr.", firstName = "David", middleName = "Patrick", lastName = "Callan";
[Benchmark(Baseline = true)]
public string StringBuilder()
{
var stringBuilder =
new StringBuilder();
return stringBuilder
.Append(title).Append(' ')
.Append(firstName).Append(' ')
.Append(middleName).Append(' ')
.Append(lastName).ToString();
}
[Benchmark]
public string StringBuilderExact24()
{
var stringBuilder =
new StringBuilder(24);
return stringBuilder
.Append(title).Append(' ')
.Append(firstName).Append(' ')
.Append(middleName).Append(' ')
.Append(lastName).ToString();
}
[Benchmark]
public string StringBuilderEstimate100()
{
var stringBuilder =
new StringBuilder(100);
return stringBuilder
.Append(title).Append(' ')
.Append(firstName).Append(' ')
.Append(middleName).Append(' ')
.Append(lastName).ToString();
}
}
}
Powered by SharpLab
// .NET 8
public string StringBuilder()
{
return new StringBuilder().Append(title).Append(' ').Append(firstName)
.Append(' ')
.Append(middleName)
.Append(' ')
.Append(lastName)
.ToString();
}
// .NET 8
public string StringBuilderExact24()
{
return new StringBuilder(24).Append(title).Append(' ').Append(firstName)
.Append(' ')
.Append(middleName)
.Append(' ')
.Append(lastName)
.ToString();
}
// .NET 8
public string StringBuilderEstimate100()
{
return new StringBuilder(100).Append(title).Append(' ').Append(firstName)
.Append(' ')
.Append(middleName)
.Append(' ')
.Append(lastName)
.ToString();
}
Powered by SharpLab
// .NET 8
.method public hidebysig
instance string StringBuilder () cil managed
{
.custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
01 00 27 00 00 00 01 5f 01 00 54 02 08 42 61 73
65 6c 69 6e 65 01
)
// Method begins at RVA 0x2050
// Code size 76 (0x4c)
.maxstack 2
// sequence point: (line 42, col 13) to (line 43, col 37) in _
IL_0000: newobj instance void [System.Runtime]System.Text.StringBuilder::.ctor()
// sequence point: (line 45, col 13) to (line 49, col 46) in _
IL_0005: ldarg.0
IL_0006: ldfld string Benchmarks.StringBuilderInitialCapacity::title
IL_000b: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(string)
IL_0010: ldc.i4.s 32
IL_0012: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(char)
IL_0017: ldarg.0
IL_0018: ldfld string Benchmarks.StringBuilderInitialCapacity::firstName
IL_001d: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(string)
IL_0022: ldc.i4.s 32
IL_0024: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(char)
IL_0029: ldarg.0
IL_002a: ldfld string Benchmarks.StringBuilderInitialCapacity::middleName
IL_002f: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(string)
IL_0034: ldc.i4.s 32
IL_0036: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(char)
IL_003b: ldarg.0
IL_003c: ldfld string Benchmarks.StringBuilderInitialCapacity::lastName
IL_0041: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(string)
IL_0046: callvirt instance string [System.Runtime]System.Object::ToString()
IL_004b: ret
}
// .NET 8
.method public hidebysig
instance string StringBuilderExact24 () cil managed
{
.custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
01 00 34 00 00 00 01 5f 00 00
)
// Method begins at RVA 0x20a8
// Code size 78 (0x4e)
.maxstack 2
// sequence point: (line 55, col 13) to (line 56, col 39) in _
IL_0000: ldc.i4.s 24
IL_0002: newobj instance void [System.Runtime]System.Text.StringBuilder::.ctor(int32)
// sequence point: (line 58, col 13) to (line 62, col 46) in _
IL_0007: ldarg.0
IL_0008: ldfld string Benchmarks.StringBuilderInitialCapacity::title
IL_000d: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(string)
IL_0012: ldc.i4.s 32
IL_0014: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(char)
IL_0019: ldarg.0
IL_001a: ldfld string Benchmarks.StringBuilderInitialCapacity::firstName
IL_001f: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(string)
IL_0024: ldc.i4.s 32
IL_0026: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(char)
IL_002b: ldarg.0
IL_002c: ldfld string Benchmarks.StringBuilderInitialCapacity::middleName
IL_0031: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(string)
IL_0036: ldc.i4.s 32
IL_0038: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(char)
IL_003d: ldarg.0
IL_003e: ldfld string Benchmarks.StringBuilderInitialCapacity::lastName
IL_0043: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(string)
IL_0048: callvirt instance string [System.Runtime]System.Object::ToString()
IL_004d: ret
}
// .NET 8
.method public hidebysig
instance string StringBuilderEstimate100 () cil managed
{
.custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
01 00 41 00 00 00 01 5f 00 00
)
// Method begins at RVA 0x2104
// Code size 78 (0x4e)
.maxstack 2
// sequence point: (line 68, col 13) to (line 69, col 40) in _
IL_0000: ldc.i4.s 100
IL_0002: newobj instance void [System.Runtime]System.Text.StringBuilder::.ctor(int32)
// sequence point: (line 71, col 13) to (line 75, col 46) in _
IL_0007: ldarg.0
IL_0008: ldfld string Benchmarks.StringBuilderInitialCapacity::title
IL_000d: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(string)
IL_0012: ldc.i4.s 32
IL_0014: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(char)
IL_0019: ldarg.0
IL_001a: ldfld string Benchmarks.StringBuilderInitialCapacity::firstName
IL_001f: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(string)
IL_0024: ldc.i4.s 32
IL_0026: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(char)
IL_002b: ldarg.0
IL_002c: ldfld string Benchmarks.StringBuilderInitialCapacity::middleName
IL_0031: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(string)
IL_0036: ldc.i4.s 32
IL_0038: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(char)
IL_003d: ldarg.0
IL_003e: ldfld string Benchmarks.StringBuilderInitialCapacity::lastName
IL_0043: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Append(string)
IL_0048: callvirt instance string [System.Runtime]System.Object::ToString()
IL_004d: ret
}
Powered by SharpLab
|
|
|
Benchmark Description:
Benchmark showing how setting initial capacity for a StringBuilder can improve performance. Mostly if we are using SB we likely don't know the final capacity, but as we can see with StringBuilderEstimate100 benchmark above even a way-off estimate can improve speed, albeit at the cost of more memory.
The perf benefit comes from the fact that the array which the SB uses internally needs less resizing.
The provided benchmark code is designed to measure and compare the performance of constructing strings using the `StringBuilder` class in C# with different initial capacities. It is set up to run under the .NET 8.0 runtime environment, indicating it targets the latest or one of the latest versions of the .NET runtime as of the knowledge cutoff in 2023. The benchmarks are configured to hide certain columns in the output for clarity and focus on the most relevant metrics. The use of the `MemoryDiagnoser` attribute indicates that memory allocation is also being measured and reported, which is crucial for understanding the memory efficiency of the different approaches.
### General Setup
- **.NET Version**: The benchmarks are specified to run on .NET 8.0 (`[SimpleJob(RuntimeMoniker.Net80)]`), ensuring the tests are executed using the latest runtime optimizations and features available in .NET.
- **Configuration**: Custom configuration is provided to adjust the summary style, particularly to display ratio styles in percentage. This makes it easier to compare the relative performance differences between the benchmark methods.
- **Memory Diagnoser**: Enabled to report on memory allocations, which helps in understanding the memory efficiency of different `StringBuilder` initialization strategies.
### Benchmark Methods
#### 1. `StringBuilder()`
- **Purpose**: This method serves as the baseline for comparison. It constructs a `StringBuilder` instance without specifying an initial capacity.
- **Performance Aspect**: It tests the default behavior of `StringBuilder` when appending strings without pre-allocating memory. This scenario is common in many applications where the final string length is unknown upfront.
- **Expected Insights**: This benchmark will show how the `StringBuilder` performs in terms of speed and memory allocation when it needs to automatically resize its internal buffer to accommodate appended strings.
#### 2. `StringBuilderExact24()`
- **Purpose**: This method tests the performance of `StringBuilder` when initialized with an exact capacity needed for the final string.
- **Performance Aspect**: It specifically measures the impact of initializing `StringBuilder` with a capacity that perfectly matches the length of the final string, thus avoiding any resizing operations.
- **Expected Insights**: The results should indicate whether pre-allocating the exact memory needed can improve performance and reduce memory allocations compared to the baseline. This method is expected to be more efficient since it eliminates the need for buffer resizing.
#### 3. `StringBuilderEstimate100()`
- **Purpose**: This method evaluates the performance when `StringBuilder` is initialized with a capacity larger than needed.
- **Performance Aspect**: It assesses how over-estimating the initial capacity affects performance and memory usage, testing the scenario where developers allocate more memory upfront to avoid resizing, possibly at the expense of using more memory than necessary.
- **Expected Insights**: The benchmark will show if and how much performance gain or loss is associated with over-allocating memory. While it may reduce or eliminate resizing operations, it could lead to higher memory usage, which might not be desirable in memory-constrained environments.
### Conclusion
Running these benchmarks will provide insights into the trade-offs between initializing `StringBuilder` with different capacities. It will help developers understand how to balance between memory efficiency and performance, especially in scenarios where `StringBuilder` is heavily used for string manipulation. The results will guide best practices for using `StringBuilder` in terms of specifying initial capacities to optimize for speed and memory usage.