1


StringBuilder.Replace performance improvements in .NET 8 v .NET 7




Date Added (UTC):

15 Apr 2024 @ 20:32

Date Updated (UTC):

16 Apr 2024 @ 03:03


.NET Version(s):

.NET 7 .NET 8

Tag(s):

#.Net8PerfImprovement #StringBuilder


Added By:
Profile Image

Blog   
Ireland    
.NET Developer and tech lead from Ireland!

Benchmark Results:





Benchmark Code:



using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Reports;
using System.Text;

[HideColumns("Error", "StdDev", "Median", "RatioSD")]
[MemoryDiagnoser(displayGenColumns: false)]
[Config(typeof(Config))]
public class StringBuilderReplace
{
    private readonly StringBuilder _sb = new StringBuilder()
        .Append("Shall I compare thee to a summer's day? ")
        .Append("Thou art more lovely and more temperate: ")
        .Append("Rough winds do shake the darling buds of May, ")
        .Append("And summer's lease hath all too short a date; ")
        .Append("Sometime too hot the eye of heaven shines, ")
        .Append("And often is his gold complexion dimm'd; ")
        .Append("And every fair from fair sometime declines, ")
        .Append("By chance or nature's changing course untrimm'd; ")
        .Append("But thy eternal summer shall not fade, ")
        .Append("Nor lose possession of that fair thou ow'st; ")
        .Append("Nor shall death brag thou wander'st in his shade, ")
        .Append("When in eternal lines to time thou grow'st: ")
        .Append("So long as men can breathe or eyes can see, ")
        .Append("So long lives this, and this gives life to thee.");

    [Benchmark]
    public void Replace()
    {
        _sb.Replace("summer", "winter");
        _sb.Replace("winter", "summer");
    }

    private class Config : ManualConfig
    {
        public Config()
        {
            AddJob(Job.Default.WithId(".NET 7").WithRuntime(CoreRuntime.Core70).AsBaseline());
            AddJob(Job.Default.WithId(".NET 8").WithRuntime(CoreRuntime.Core80));

            SummaryStyle =
                SummaryStyle.Default.WithRatioStyle(RatioStyle.Percentage);
        }
    }
}

// .NET 7, .NET 8
public void Replace()
{
    _sb.Replace("summer", "winter");
    _sb.Replace("winter", "summer");
}

// .NET 7
.method public hidebysig 
    instance void Replace () cil managed 
{
    .custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
        01 00 29 00 00 00 01 5f 00 00
    )
    // Method begins at RVA 0x208e
    // Code size 45 (0x2d)
    .maxstack 8

    // sequence point: (line 44, col 9) to (line 44, col 41) in _
    IL_0000: ldarg.0
    IL_0001: ldfld class [System.Runtime]System.Text.StringBuilder StringBuilderReplace::_sb
    IL_0006: ldstr "summer"
    IL_000b: ldstr "winter"
    IL_0010: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Replace(string, string)
    IL_0015: pop
    // sequence point: (line 45, col 9) to (line 45, col 41) in _
    IL_0016: ldarg.0
    IL_0017: ldfld class [System.Runtime]System.Text.StringBuilder StringBuilderReplace::_sb
    IL_001c: ldstr "winter"
    IL_0021: ldstr "summer"
    IL_0026: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Replace(string, string)
    IL_002b: pop
    // sequence point: (line 46, col 5) to (line 46, col 6) in _
    IL_002c: ret
}

// .NET 8
.method public hidebysig 
    instance void Replace () cil managed 
{
    .custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
        01 00 29 00 00 00 01 5f 00 00
    )
    // Method begins at RVA 0x2050
    // Code size 45 (0x2d)
    .maxstack 8

    // sequence point: (line 44, col 9) to (line 44, col 41) in _
    IL_0000: ldarg.0
    IL_0001: ldfld class [System.Runtime]System.Text.StringBuilder StringBuilderReplace::_sb
    IL_0006: ldstr "summer"
    IL_000b: ldstr "winter"
    IL_0010: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Replace(string, string)
    IL_0015: pop
    // sequence point: (line 45, col 9) to (line 45, col 41) in _
    IL_0016: ldarg.0
    IL_0017: ldfld class [System.Runtime]System.Text.StringBuilder StringBuilderReplace::_sb
    IL_001c: ldstr "winter"
    IL_0021: ldstr "summer"
    IL_0026: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Replace(string, string)
    IL_002b: pop
    // sequence point: (line 46, col 5) to (line 46, col 6) in _
    IL_002c: ret
}

// .NET 7 (X64)
Replace()
    L0000: push rdi
    L0001: push rsi
    L0002: push rbx
    L0003: sub rsp, 0x30
    L0007: mov rsi, rcx
    L000a: mov rcx, [rsi+8]
    L000e: mov edx, [rcx+0x1c]
    L0011: add edx, [rcx+0x18]
    L0014: mov [rsp+0x20], edx
    L0018: mov rdx, 0x29f1fc90380
    L0022: mov rdi, [rdx]
    L0025: mov rdx, rdi
    L0028: mov r8, 0x29f1fc90388
    L0032: mov rbx, [r8]
    L0035: mov r8, rbx
    L0038: xor r9d, r9d
    L003b: call qword ptr [0x7ffce9e13960]
    L0041: mov rcx, [rsi+8]
    L0045: mov edx, [rcx+0x1c]
    L0048: add edx, [rcx+0x18]
    L004b: mov [rsp+0x20], edx
    L004f: mov rdx, rbx
    L0052: mov r8, rdi
    L0055: xor r9d, r9d
    L0058: call qword ptr [0x7ffce9e13960]
    L005e: nop
    L005f: add rsp, 0x30
    L0063: pop rbx
    L0064: pop rsi
    L0065: pop rdi
    L0066: ret
// .NET 8 (X64)
Replace()
    L0000: push rdi
    L0001: push rsi
    L0002: push rbx
    L0003: sub rsp, 0x30
    L0007: mov rbx, rcx
    L000a: mov rcx, [rbx+8]
    L000e: mov edx, [rcx+0x1c]
    L0011: add edx, [rcx+0x18]
    L0014: mov [rsp+0x20], edx
    L0018: mov rdx, 0x1bf05065560
    L0022: mov rsi, [rdx]
    L0025: mov rdx, rsi
    L0028: mov r8, 0x1bf05065568
    L0032: mov rdi, [r8]
    L0035: mov r8, rdi
    L0038: xor r9d, r9d
    L003b: call qword ptr [0x7ff9db3bd920]
    L0041: mov rcx, [rbx+8]
    L0045: mov edx, [rcx+0x1c]
    L0048: add edx, [rcx+0x18]
    L004b: mov [rsp+0x20], edx
    L004f: mov rdx, rdi
    L0052: mov r8, rsi
    L0055: xor r9d, r9d
    L0058: call qword ptr [0x7ff9db3bd920]
    L005e: nop
    L005f: add rsp, 0x30
    L0063: pop rbx
    L0064: pop rsi
    L0065: pop rdi
    L0066: ret


Benchmark Description:


This benchmark setup is designed to measure the performance of the `StringBuilder.Replace` method in different .NET versions, specifically comparing .NET 7 and .NET 8. The benchmark is configured using BenchmarkDotNet, a powerful .NET library for benchmarking, which provides detailed insights into the performance of the code being tested. ### General Setup Overview - **.NET Versions:** The benchmark compares .NET 7 and .NET 8, using these versions to understand potential performance improvements or regressions in the `StringBuilder.Replace` method across these versions. - **Configuration:** The benchmark uses a custom configuration class (`Config`) to specify the .NET versions to test against. It sets .NET 7 as the baseline for comparison purposes. - **Memory Diagnoser:** Enabled with `displayGenColumns: false` to report memory allocation without displaying garbage collection generation columns. This focuses on the amount of memory allocated by the benchmarked methods. - **Columns:** The setup hides specific columns ("Error", "StdDev", "Median", "RatioSD") to focus on the most relevant data for this benchmark, such as mean time and allocated memory. - **BenchmarkDotNet Attributes:** Attributes like `[MemoryDiagnoser]` and `[Config]` are used to apply the memory diagnoser and the custom configuration to the benchmark class. ### Benchmark Method: `Replace()` #### Purpose The `Replace()` method benchmark is designed to measure the performance of replacing strings within a `StringBuilder` instance. It performs two replacements in sequence, first replacing "summer" with "winter" and then "winter" with "summer". This method tests the efficiency of the `StringBuilder.Replace` operation, which is a common operation when manipulating strings in .NET. #### Performance Aspect - **Execution Time:** How quickly the `StringBuilder.Replace` method can perform the specified replacements. - **Memory Allocation:** The amount of memory allocated during the replacement operations. Efficient memory usage is crucial for high-performance applications, especially those that perform a large number of string manipulations. #### Importance Understanding the performance of the `StringBuilder.Replace` method is important for developers who rely on `StringBuilder` for creating and manipulating strings in performance-critical applications. Since `StringBuilder` is often used to improve performance over immutable string operations, knowing how its replace functionality scales with larger or more complex strings can inform optimization decisions. #### Expected Results or Insights - **Performance Improvement or Regression:** By comparing .NET 7 and .NET 8, developers can identify any performance improvements or regressions in the `StringBuilder.Replace` method. An improvement in .NET 8 would indicate optimizations in the newer runtime version. - **Efficiency of Replace Operation:** The benchmark will highlight the efficiency of the replace operation in terms of execution speed and memory allocation. Lower memory allocation and faster execution times are indicative of better performance. - **Decision Making:** Results from this benchmark can help developers decide on the most efficient .NET version for their applications or identify potential areas for code optimization when using `StringBuilder.Replace`. In summary, this benchmark is a targeted test to understand the performance characteristics of the `StringBuilder.Replace` method across different .NET runtime versions, focusing on execution time and memory allocation. The insights gained can guide developers in optimizing their applications for better performance.


Benchmark Comments: