1
string.Equals v string.Compare v ToLower/Upper for case insensitive string comparison on .NET 8
Date Added (UTC):
05 Apr 2024 @ 03:57
Date Updated (UTC):05 Apr 2024 @ 03:57
.NET Version(s): Tag(s):
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;
namespace BenchmarkDotNet.Samples
{
[MemoryDiagnoser]
[Config(typeof(Config))]
[SimpleJob(RuntimeMoniker.Net80)]
[HideColumns(Column.Job, Column.RatioSD, Column.AllocRatio)]
public class StringComparisonBenchmarks
{
private class Config : ManualConfig
{
public Config()
{
SummaryStyle =
SummaryStyle.Default.WithRatioStyle(RatioStyle.Percentage);
}
}
private string str1 = "HELLO WORLD";
private string str2 = "hello world";
[Benchmark(Baseline = true)]
public bool Equals_OrdinalIgnoreCase() => string.Equals(str1, str2, StringComparison.OrdinalIgnoreCase);
[Benchmark]
public bool Compare_OrdinalIgnoreCase() => string.Compare(str1, str2, StringComparison.OrdinalIgnoreCase) == 0;
[Benchmark]
public bool ToLower() => str1.ToLower() == str2.ToLower();
[Benchmark]
public bool ToUpper() => str1.ToUpper() == str2.ToUpper();
}
}
Powered by SharpLab
// .NET 8
public bool Equals_OrdinalIgnoreCase()
{
return string.Equals(str1, str2, StringComparison.OrdinalIgnoreCase);
}
// .NET 8
public bool Compare_OrdinalIgnoreCase()
{
return string.Compare(str1, str2, StringComparison.OrdinalIgnoreCase) == 0;
}
// .NET 8
public bool ToLower()
{
return str1.ToLower() == str2.ToLower();
}
// .NET 8
public bool ToUpper()
{
return str1.ToUpper() == str2.ToUpper();
}
Powered by SharpLab
// .NET 8
.method public hidebysig
instance bool Equals_OrdinalIgnoreCase () 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 19 (0x13)
.maxstack 8
// sequence point: (line 40, col 51) to (line 40, col 112) in _
IL_0000: ldarg.0
IL_0001: ldfld string BenchmarkDotNet.Samples.StringComparisonBenchmarks::str1
IL_0006: ldarg.0
IL_0007: ldfld string BenchmarkDotNet.Samples.StringComparisonBenchmarks::str2
IL_000c: ldc.i4.5
IL_000d: call bool [System.Runtime]System.String::Equals(string, string, valuetype [System.Runtime]System.StringComparison)
IL_0012: ret
}
// .NET 8
.method public hidebysig
instance bool Compare_OrdinalIgnoreCase () cil managed
{
.custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
01 00 2a 00 00 00 01 5f 00 00
)
// Method begins at RVA 0x2064
// Code size 22 (0x16)
.maxstack 8
// sequence point: (line 43, col 52) to (line 43, col 119) in _
IL_0000: ldarg.0
IL_0001: ldfld string BenchmarkDotNet.Samples.StringComparisonBenchmarks::str1
IL_0006: ldarg.0
IL_0007: ldfld string BenchmarkDotNet.Samples.StringComparisonBenchmarks::str2
IL_000c: ldc.i4.5
IL_000d: call int32 [System.Runtime]System.String::Compare(string, string, valuetype [System.Runtime]System.StringComparison)
IL_0012: ldc.i4.0
IL_0013: ceq
IL_0015: ret
}
// .NET 8
.method public hidebysig
instance bool ToLower () cil managed
{
.custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
01 00 2d 00 00 00 01 5f 00 00
)
// Method begins at RVA 0x207b
// Code size 28 (0x1c)
.maxstack 8
// sequence point: (line 46, col 34) to (line 46, col 66) in _
IL_0000: ldarg.0
IL_0001: ldfld string BenchmarkDotNet.Samples.StringComparisonBenchmarks::str1
IL_0006: callvirt instance string [System.Runtime]System.String::ToLower()
IL_000b: ldarg.0
IL_000c: ldfld string BenchmarkDotNet.Samples.StringComparisonBenchmarks::str2
IL_0011: callvirt instance string [System.Runtime]System.String::ToLower()
IL_0016: call bool [System.Runtime]System.String::op_Equality(string, string)
IL_001b: ret
}
// .NET 8
.method public hidebysig
instance bool ToUpper () cil managed
{
.custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
01 00 30 00 00 00 01 5f 00 00
)
// Method begins at RVA 0x2098
// Code size 28 (0x1c)
.maxstack 8
// sequence point: (line 49, col 34) to (line 49, col 66) in _
IL_0000: ldarg.0
IL_0001: ldfld string BenchmarkDotNet.Samples.StringComparisonBenchmarks::str1
IL_0006: callvirt instance string [System.Runtime]System.String::ToUpper()
IL_000b: ldarg.0
IL_000c: ldfld string BenchmarkDotNet.Samples.StringComparisonBenchmarks::str2
IL_0011: callvirt instance string [System.Runtime]System.String::ToUpper()
IL_0016: call bool [System.Runtime]System.String::op_Equality(string, string)
IL_001b: ret
}
Powered by SharpLab
|
|
|
|
Benchmark Description:
string.Equals doesn't need to create strings like ToUpper/ToLower.
In this case the strings match, but if they didn't the perf didn't would be even larger as string.Equals ***can exit early*** upon the first difference unlike ToUpper/ToLower.
The provided benchmark code is designed to measure and compare the performance of different methods for case-insensitive string comparison in .NET. This benchmark is set up using BenchmarkDotNet, a powerful .NET library for benchmarking code performance. The configuration and specific benchmarks are designed to provide insights into the efficiency and resource usage of various string comparison techniques. Here's an overview of the general setup and the rationale behind each benchmark method:
### General Setup
- **.NET Version**: The benchmark specifies `[SimpleJob(RuntimeMoniker.Net80)]`, indicating that it targets .NET 8.0. This is important as performance characteristics can vary between different .NET runtime versions due to optimizations and changes in the framework.
- **MemoryDiagnoser**: Enabled by `[MemoryDiagnoser]`, this feature collects and reports memory allocation data, which is crucial for understanding the memory efficiency of each method.
- **Config**: The custom configuration class `Config` modifies the summary style to display ratios in percentage format, making the results easier to interpret.
- **HideColumns**: Certain columns are hidden (`Column.Job`, `Column.RatioSD`, `Column.AllocRatio`) to focus on the most relevant data for this benchmark.
### Benchmark Methods
#### 1. `Equals_OrdinalIgnoreCase`
- **Purpose**: Measures the performance of case-insensitive string comparison using `string.Equals` with `StringComparison.OrdinalIgnoreCase`.
- **Rationale**: This method is commonly recommended for case-insensitive comparisons because it's optimized for performance and correctly handles cultural differences without converting the case of the strings.
- **Expected Insights**: This benchmark is expected to show good performance with minimal memory allocations, serving as a baseline for comparison with other methods.
#### 2. `Compare_OrdinalIgnoreCase`
- **Purpose**: Tests the performance of using `string.Compare` with `StringComparison.OrdinalIgnoreCase` for case-insensitive string comparison, checking for equality by comparing the result to 0.
- **Rationale**: Similar to `Equals_OrdinalIgnoreCase`, but uses a different API that might have different performance characteristics. It's important to compare both to understand which is more efficient for equality checks.
- **Expected Insights**: Likely to have performance close to `Equals_OrdinalIgnoreCase`, but the comparison might introduce slight overhead. Memory usage should be comparable.
#### 3. `ToLower`
- **Purpose**: Evaluates the performance of converting both strings to lowercase using `ToLower` and then comparing them for equality.
- **Rationale**: This approach is sometimes used for case-insensitive comparison but can be less efficient due to the creation of new string instances and potential cultural differences in case conversion.
- **Expected Insights**: This method is expected to show higher memory allocations due to the creation of new strings and potentially slower performance compared to ordinal comparison methods.
#### 4. `ToUpper`
- **Purpose**: Similar to `ToLower`, this benchmark measures the performance of converting both strings to uppercase using `ToUpper` before comparing them.
- **Rationale**: This is another common technique for case-insensitive comparison. It's useful to compare its performance and memory usage against `ToLower` and ordinal methods.
- **Expected Insights**: Like `ToLower`, higher memory allocations and possibly slower performance are expected due to the creation of new strings. The choice between `ToUpper` and `ToLower` might also be influenced by specific cultural or linguistic considerations.
### Conclusion
Running these benchmarks provides insights into the efficiency (both in terms of execution time and memory usage) of different strategies for case-insensitive string comparison in .NET. This can guide developers in choosing the most appropriate method based on their specific requirements, balancing performance and correctness (e.g., handling of cultural differences).