-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Add reference equality to unrolled string/span equality against constant strings #117431
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
This comment was marked as resolved.
This comment was marked as resolved.
|
@EgorBot -arm -amd using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
public class BestCase
{
string _empty;
[Benchmark] public bool Best_StringEq_Empty() => _empty == "";
string _short = "Hi";
[Benchmark] public bool Best_StringEq_Short() => _short == "H1";
[Benchmark] public bool Best_StringEq_Short_IgnoreCase() => string.Equals(_short, "H1", StringComparison.OrdinalIgnoreCase);
string _medium = "Hello World";
[Benchmark] public bool Best_StringEq_Medium() => _medium == "Hello World";
[Benchmark] public bool Best_StringEq_Medium_IgnoreCase() => string.Equals(_medium, "Hello World", StringComparison.OrdinalIgnoreCase);
string _long = "using System.Runtime.CompilerServices";
[Benchmark] public bool Best_StringEq_Long() => _long == "using System.Runtime.CompilerServices";
[Benchmark] public bool Best_StringEq_Long_IgnoreCase() => string.Equals(_long, "using System.Runtime.CompilerServices", StringComparison.OrdinalIgnoreCase);
[Benchmark] public bool Best_SpanEq_Long() => _long.AsSpan().Equals("using System.Runtime.CompilerServices", StringComparison.Ordinal);
[Benchmark] public bool Best_SpanEq_Long_IgnoreCase() => _long.AsSpan().Equals("using System.Runtime.CompilerServices", StringComparison.OrdinalIgnoreCase);
}
public class WorstCase
{
// Don't know how to create an empty string that will be different from the interned one
// I think it's almost impossible in practice.
string _short = "Qi";
[Benchmark] public bool Worst_StringEq_Short() => _short == "H1";
[Benchmark] public bool Worst_StringEq_Short_IgnoreCase() => string.Equals(_short, "H1", StringComparison.OrdinalIgnoreCase);
string _medium = "Qello World";
[Benchmark] public bool Worst_StringEq_Medium() => _medium == "Hello World";
[Benchmark] public bool Worst_StringEq_Medium_IgnoreCase() => string.Equals(_medium, "Hello World", StringComparison.OrdinalIgnoreCase);
string _long = "Qsing System.Runtime.CompilerServices";
[Benchmark] public bool Worst_StringEq_Long() => _long == "using System.Runtime.CompilerServices";
[Benchmark] public bool Worst_StringEq_Long_IgnoreCase() => string.Equals(_long, "using System.Runtime.CompilerServices", StringComparison.OrdinalIgnoreCase);
[Benchmark] public bool Worst_SpanEq_Long() => _long.AsSpan().Equals("Qsing System.Runtime.CompilerServices", StringComparison.Ordinal);
[Benchmark] public bool Worst_SpanEq_Long_IgnoreCase() => _long.AsSpan().Equals("Qsing System.Runtime.CompilerServices", StringComparison.OrdinalIgnoreCase);
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR enhances JIT unrolled comparisons by reinserting a reference equality check when comparing strings or spans against constant strings for the Equals case.
- Wraps the existing unrolled string comparison in a ternary
(str == const ? true : unrolled)to restore the fast path. - Applies an equivalent reference check for span comparisons.
- Leaves
StartsWith/EndsWithunmodified.
Comments suppressed due to low confidence (1)
src/coreclr/jit/importervectorization.cpp:510
- Add JIT unit tests that verify
string.Equalsagainst constant strings uses the reference‐equality fast path when the instance matches the constant, and correctly falls back to the unrolled comparison otherwise.
// Wrap with the reference equality check for Equals.
|
PTAL @dotnet/jit-contrib This adds a missing reference equality fast-path to the unrolling. It does exists in the reference version: runtime/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs Lines 692 to 695 in 3f240ca
so when the unrolling triggers, it might regress the performance. The overhead seems to be around noise. |
|
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch |
Jit knows how to unroll
string.Equals/MemoryExtensions.SequenceEqualswhen one of its args is a constant string. When it does so, it doesn't check for reference equality, which can lead to a regression compared to the non-unrolled behavior (can be quite significant). It's hard to say how often that happens on practice (we may involve Dynamic PGO for that, I might do that in a separate PR), but looks like the overhead from the reference check is not too bad. This issue led to terrible benchmark results for this benchmark: #117427 (comment) when that PR causes the unrolling to kick in and the previous fast path with reference equality was not presented in the codegen.Example:
Asm diff: https://www.diffchecker.com/6ZrwEyJb/