1


IndexOf versus StartsWith on .NET 8




Date Added (UTC):

08 Apr 2024 @ 00:47

Date Updated (UTC):

08 Apr 2024 @ 01:15


.NET Version(s):

.NET 8

Tag(s):

#StringComparison #Strings


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;

[MemoryDiagnoser]
[Config(typeof(Config))]
[HideColumns("Error", "StdDev", "Median", "RatioSD")]
public class IndexOfStartswithBenchmark
{
    private readonly string _haystack = """
        It was the best of times, it was the worst of times,
        it was the age of wisdom, it was the age of foolishness,
        it was the epoch of belief, it was the epoch of incredulity,
        it was the season of light, it was the season of darkness,
        it was the spring of hope, it was the winter of despair.
        """;

    private readonly string _needle = "hello";

    [Benchmark(Baseline = true)]
    public bool StartsWith_IndexOf0() =>
        _haystack.IndexOf(_needle, StringComparison.OrdinalIgnoreCase) == 0;

    [Benchmark]
    public bool StartsWith_StartsWith() =>
        _haystack.StartsWith(_needle, StringComparison.OrdinalIgnoreCase);

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

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

// .NET 8
public bool StartsWith_IndexOf0()
{
    return _haystack.IndexOf(_needle, StringComparison.OrdinalIgnoreCase) == 0;
}
// .NET 8
public bool StartsWith_StartsWith()
{
    return _haystack.StartsWith(_needle, StringComparison.OrdinalIgnoreCase);
}

// .NET 8
.method public hidebysig 
    instance bool StartsWith_IndexOf0 () cil managed 
{
    .custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
        01 00 23 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 22 (0x16)
    .maxstack 8

    // sequence point: (line 37, col 9) to (line 37, col 76) in _
    IL_0000: ldarg.0
    IL_0001: ldfld string IndexOfStartswithBenchmark::_haystack
    IL_0006: ldarg.0
    IL_0007: ldfld string IndexOfStartswithBenchmark::_needle
    IL_000c: ldc.i4.5
    IL_000d: callvirt instance int32 [System.Runtime]System.String::IndexOf(string, valuetype [System.Runtime]System.StringComparison)
    IL_0012: ldc.i4.0
    IL_0013: ceq
    IL_0015: ret
}
// .NET 8
.method public hidebysig 
    instance bool StartsWith_StartsWith () cil managed 
{
    .custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
        01 00 27 00 00 00 01 5f 00 00
    )
    // Method begins at RVA 0x2067
    // Code size 19 (0x13)
    .maxstack 8

    // sequence point: (line 41, col 9) to (line 41, col 74) in _
    IL_0000: ldarg.0
    IL_0001: ldfld string IndexOfStartswithBenchmark::_haystack
    IL_0006: ldarg.0
    IL_0007: ldfld string IndexOfStartswithBenchmark::_needle
    IL_000c: ldc.i4.5
    IL_000d: callvirt instance bool [System.Runtime]System.String::StartsWith(string, valuetype [System.Runtime]System.StringComparison)
    IL_0012: ret
}

// .NET 8 (X64)
StartsWith_IndexOf0()
    L0000: sub rsp, 0x28
    L0004: mov rdx, [rcx+8]
    L0008: mov r8, [rcx+0x10]
    L000c: mov dword ptr [rsp+0x20], 5
    L0014: mov r9d, [rdx+8]
    L0018: mov rcx, rdx
    L001b: mov rdx, r8
    L001e: xor r8d, r8d
    L0021: call qword ptr [0x7ff8a85975d0]
    L0027: test eax, eax
    L0029: sete al
    L002c: movzx eax, al
    L002f: add rsp, 0x28
    L0033: ret
// .NET 8 (X64)
StartsWith_StartsWith()
    L0000: mov rdx, rcx
    L0003: mov rcx, [rdx+8]
    L0007: mov rdx, [rdx+0x10]
    L000b: mov r8d, 5
    L0011: cmp [rcx], ecx
    L0013: jmp qword ptr [0x7ff8a85962f8]


Benchmark Description:


IndexOf where the result is then being checked for equality with 0 is functionally the same as a call to StartsWith, but is much more expensive as it could end up examining the entire source string rather than just the starting position. ***Note***, a new code rule was added in .NET 8 to detect this, [CA1858](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/CA1858)

The provided benchmark code is designed to compare the performance and efficiency of two different methods for checking if a string starts with a specified substring in C#. The benchmark is set up using BenchmarkDotNet, a powerful .NET library for benchmarking code performance. The configuration specifies the use of .NET 8 (CoreRuntime.Core80), indicating that the benchmarks are intended to be run using the .NET 8 runtime environment. This is significant as performance characteristics can vary between different .NET versions due to optimizations and improvements in the runtime and base class libraries. ### General Setup - **.NET Version:** The benchmark specifies the use of .NET 8, which is the targeted runtime environment. This is important for ensuring that the benchmarks are run under a consistent and modern runtime that reflects the latest performance improvements and features. - **BenchmarkDotNet Configuration:** The benchmark uses a custom configuration class (`Config`) that extends `ManualConfig`. It specifies the use of .NET 8 and customizes the summary style to display ratios in percentage format. This helps in easily comparing the performance differences between the benchmarked methods. - **Memory Diagnoser:** The `[MemoryDiagnoser]` attribute is used to include memory allocation metrics in the benchmark results. This is crucial for understanding the memory efficiency of the compared methods in addition to their execution speed. - **Columns Configuration:** The `[HideColumns]` attribute is used to simplify the output by hiding certain columns like "Error", "StdDev" (standard deviation), "Median", and "RatioSD" (ratio standard deviation). This focuses the results on the most relevant metrics for this comparison. ### Benchmark Methods #### 1. `StartsWith_IndexOf0()` - **Purpose:** This method tests the performance of using the `IndexOf` method to check if the string `_haystack` starts with the substring `_needle` by comparing the index of the first occurrence of `_needle` in `_haystack` to `0`. If `_needle` is found at the start of `_haystack`, `IndexOf` returns `0`. - **Performance Aspect:** This benchmark measures the efficiency of using `IndexOf` for start-of-string checks, including the execution time and any additional overhead when the substring is not at the beginning. - **Expected Insights:** This method might be less efficient for start-of-string checks compared to `StartsWith` due to potentially scanning more of the `_haystack` string than necessary when `_needle` is not at the beginning. The results will show how significant this inefficiency is in terms of execution time and memory usage. #### 2. `StartsWith_StartsWith()` - **Purpose:** This method evaluates the performance of the `StartsWith` method for checking if `_haystack` starts with `_needle`. This method is specifically designed for this purpose and takes into account the specified string comparison option (`StringComparison.OrdinalIgnoreCase`). - **Performance Aspect:** The benchmark aims to measure the execution speed and memory efficiency of the `StartsWith` method, which is expected to be optimized for this specific use case. - **Expected Insights:** Since `StartsWith` is designed for checking if a string starts with a specified substring, it is anticipated to be more efficient than using `IndexOf` for the same purpose. The benchmark results should reflect this by showing faster execution times and possibly lower memory allocations for `StartsWith_StartsWith()` compared to `StartsWith_IndexOf0()`. ### Conclusion By running these benchmarks, you can gain insights into the relative performance of using `IndexOf` versus `StartsWith` for start-of-string checks in C#. This can guide optimization efforts in scenarios where this type of string operation is critical for performance. The expected outcome is that `StartsWith` will be more efficient both in terms of execution speed and memory usage, highlighting the importance of choosing the right method for specific string operations.


Benchmark Comments: