String.Replace .NET 8 v .NET 7 performance benchmarks




Date Added (UTC):

15 Apr 2024 @ 20:13

Date Updated (UTC):

15 Apr 2024 @ 20:13


.NET Version(s):

.NET 7 .NET 8

Tag(s):

#.Net8PerfImprovement #StringManipulation


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 StringReplaceDotnet8
{
    private readonly StringBuilder _sb = 
        new StringBuilder("http://server\\this\\is\\a\\test\\of\\needing\\to\\normalize\\directory\\separators\\");

    [Benchmark]
    public void Replace()
    {
        _sb.Replace('\\', '/');
        _sb.Replace('/', '\\');
    }

    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('\\', '/');
    _sb.Replace('/', '\\');
}

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

    // sequence point: (line 31, col 9) to (line 31, col 32) in _
    IL_0000: ldarg.0
    IL_0001: ldfld class [System.Runtime]System.Text.StringBuilder StringReplaceDotnet8::_sb
    IL_0006: ldc.i4.s 92
    IL_0008: ldc.i4.s 47
    IL_000a: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Replace(char, char)
    IL_000f: pop
    // sequence point: (line 32, col 9) to (line 32, col 32) in _
    IL_0010: ldarg.0
    IL_0011: ldfld class [System.Runtime]System.Text.StringBuilder StringReplaceDotnet8::_sb
    IL_0016: ldc.i4.s 47
    IL_0018: ldc.i4.s 92
    IL_001a: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Replace(char, char)
    IL_001f: pop
    // sequence point: (line 33, col 5) to (line 33, col 6) in _
    IL_0020: ret
}

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

    // sequence point: (line 31, col 9) to (line 31, col 32) in _
    IL_0000: ldarg.0
    IL_0001: ldfld class [System.Runtime]System.Text.StringBuilder StringReplaceDotnet8::_sb
    IL_0006: ldc.i4.s 92
    IL_0008: ldc.i4.s 47
    IL_000a: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Replace(char, char)
    IL_000f: pop
    // sequence point: (line 32, col 9) to (line 32, col 32) in _
    IL_0010: ldarg.0
    IL_0011: ldfld class [System.Runtime]System.Text.StringBuilder StringReplaceDotnet8::_sb
    IL_0016: ldc.i4.s 47
    IL_0018: ldc.i4.s 92
    IL_001a: callvirt instance class [System.Runtime]System.Text.StringBuilder [System.Runtime]System.Text.StringBuilder::Replace(char, char)
    IL_001f: pop
    // sequence point: (line 33, col 5) to (line 33, col 6) in _
    IL_0020: ret
}

// .NET 7 (X64)
Replace()
    L0000: push rsi
    L0001: sub rsp, 0x30
    L0005: mov rsi, rcx
    L0008: mov rcx, [rsi+8]
    L000c: mov edx, [rcx+0x1c]
    L000f: add edx, [rcx+0x18]
    L0012: mov [rsp+0x20], edx
    L0016: mov edx, 0x5c
    L001b: mov r8d, 0x2f
    L0021: xor r9d, r9d
    L0024: call qword ptr [0x7ffa2c0d3990]
    L002a: mov rcx, [rsi+8]
    L002e: mov edx, [rcx+0x1c]
    L0031: add edx, [rcx+0x18]
    L0034: mov [rsp+0x20], edx
    L0038: mov edx, 0x2f
    L003d: mov r8d, 0x5c
    L0043: xor r9d, r9d
    L0046: call qword ptr [0x7ffa2c0d3990]
    L004c: nop
    L004d: add rsp, 0x30
    L0051: pop rsi
    L0052: ret
// .NET 8 (X64)
Replace()
    L0000: push rbx
    L0001: sub rsp, 0x30
    L0005: mov rbx, rcx
    L0008: mov rcx, [rbx+8]
    L000c: mov edx, [rcx+0x1c]
    L000f: add edx, [rcx+0x18]
    L0012: mov [rsp+0x20], edx
    L0016: mov edx, 0x5c
    L001b: mov r8d, 0x2f
    L0021: xor r9d, r9d
    L0024: call qword ptr [0x7ff9db3bd950]
    L002a: mov rcx, [rbx+8]
    L002e: mov edx, [rcx+0x1c]
    L0031: add edx, [rcx+0x18]
    L0034: mov [rsp+0x20], edx
    L0038: mov edx, 0x2f
    L003d: mov r8d, 0x5c
    L0043: xor r9d, r9d
    L0046: call qword ptr [0x7ff9db3bd950]
    L004c: nop
    L004d: add rsp, 0x30
    L0051: pop rbx
    L0052: ret


Benchmark Description:


The provided benchmark code is designed to measure the performance of string replacement operations in .NET, specifically comparing the performance between two versions of the .NET runtime: .NET 7 and .NET 8. This benchmark is set up using BenchmarkDotNet, a powerful .NET library that makes it easy to create benchmarks that are accurate and reliable. Let's break down the setup and the rationale behind the benchmark method provided. ### General Setup - **.NET Versions:** The benchmark is configured to test against two different .NET versions: .NET 7 and .NET 8. This is specified in the `Config` class where two jobs are added, each targeting a different .NET Core runtime version. This allows for a direct comparison of performance between these two versions for the same operation. - **BenchmarkDotNet Configuration:** The benchmark class is annotated with several attributes that configure how BenchmarkDotNet behaves and how the results are reported. The `HideColumns` attribute is used to simplify the output by hiding certain columns that may not be relevant for this specific test. The `MemoryDiagnoser` attribute with `displayGenColumns: false` is used to report memory allocation without showing garbage collection generation columns, focusing on the total memory allocated. - **StringBuilder Instance:** A `StringBuilder` instance is initialized with a test string that represents a hypothetical file path. This string includes backslashes (`\`) that are commonly used in Windows file paths but might need to be replaced with slashes (`/`) for compatibility with systems or formats that use the Unix-style path separator. ### Benchmark Method: `Replace()` - **Purpose:** The `Replace` method benchmarks the performance of replacing characters within a `StringBuilder` instance. It performs two replacements: first, it replaces all backslashes (`\`) with slashes (`/`), and then it replaces all slashes (`/`) back with backslashes (`\`). This simulates a scenario where a path might need to be normalized to different formats, a common task in file path manipulation and web URL normalization. - **Performance Aspect Tested:** This benchmark is designed to test the efficiency of the `StringBuilder.Replace` method in handling character replacements within strings. It measures how quickly and with how much memory overhead these operations can be performed. This is important because string manipulation, especially in the context of paths and URLs, is a frequent operation in many applications. Efficient string manipulation can lead to better overall application performance and reduced memory usage. - **Expected Results/Insights:** By running this benchmark, you can expect to gain insights into how the performance of the `StringBuilder.Replace` method has potentially improved or regressed between .NET 7 and .NET 8. An improvement in performance could be reflected through faster execution times and/or reduced memory allocations in the newer .NET version. Conversely, a regression would be indicated by slower execution times or increased memory allocations. Understanding these performance characteristics can help developers make informed decisions about which .NET version to target and how to optimize their string manipulation code. In summary, this benchmark is a targeted test designed to evaluate and compare the performance of a specific string manipulation operation across different versions of the .NET runtime. It highlights the utility of BenchmarkDotNet in setting up precise and meaningful performance tests to guide optimization efforts and technology choices.


Benchmark Comments: