-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Improve performance of String.StartsWith(constant string) #27682
Comments
For reference, note that Invariant and Ordinal are not the same thing; the actual comparison for your CharTest would be with OrdinalIgnoreCase rather than InvariantCultureIgnoreCase, in which case you should see a significant difference, e.g. on .NET Core 2.1 adding an OrdinalIgnoreCase variant:
And StartsWith + OrdinalIgnoreCase has already been significantly improved since 2.1. Here's the same test running on current coreclr bits for .NET Core 3.0:
|
@thargol1 on 3.0, is StartsWith fast enough for your scenario -- or at least, fast enough in all but one or two cases where it is not too painful for you to break out into chars? In my career I have only in one scenario found a need to break out into chars because the path was so hot. I am not sure this is common enough for the JIT work to be done to recognize StartsWith + Ordinal + short constant string. |
@danmosemsft .Net core 3.0 seems fast enough for me. I did some further investigation on .Net Framework and .Net core 2.2. A part of the performance difference is due to the fact that StartsWith internally calls a version of string compare ordinal ignorecase. When the characters do not match string compare performs an extra calculation to return a value less or greater than zero. For StartsWith a simple A very small performance can be gained using xor: Part of my test code: const uint ZA = (uint)('z' - 'a');
char charA = source[length];
char charB = pattern[length];
uint x = (uint)charA ^ (uint)charB;
if (x == 0 || ((x == 0x20) && (uint)((charA | 0x20) - 'a') <= ZA))
{
length--;
}
else
{
return false;
} Perhaps you can test slightly altered code in 3.0 to speed up it a little bit more (less than 5%). |
@thargol1 missed your response. Do you want to offer a PR for this, with your measurements? |
@danmosemsft I don't do PR's currently, simply because I don't know how it works, still have to read the github manual on that. This my testcode. I tried to isolate the actual test-code as much as possible, so the results are as accurate as possible. Testcode: [ReturnValueValidator]
public class CharCompareOrdinalTest
{
[Params('a','A','0')]
public int A;
[Params('a','B','[')]
public int B;
[Benchmark(Baseline =true)]
public bool Original() => OrginalCharOrdinalCompare(A, B);
[Benchmark]
public bool Custom() => CustomCharOrdinalCompare(A, B);
public static bool OrginalCharOrdinalCompare(int charA, int charB)
{
// https://github.com/Microsoft/referencesource/blob/master/mscorlib/system/string.cs
if ((uint)(charA - 'a') <= (uint)('z' - 'a')) charA -= 0x20;
if ((uint)(charB - 'a') <= (uint)('z' - 'a')) charB -= 0x20;
return charA == charB;
}
public static bool CustomCharOrdinalCompare(int charA, int charB)
{
uint x = (uint)charA ^ (uint)charB;
return x == 0 || ((x == 0x20) && (uint)((charA | 0x20) - 'a') <= (uint)('z' - 'a'));
}
} Results: BenchmarkDotNet=v0.11.4, OS=Windows 10.0.17763.379 (1809/October2018Update/Redstone5)
Intel Core i7-3820 CPU 3.60GHz (Ivy Bridge), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.0.100-preview3-010431
[Host] : .NET Core 3.0.0-preview3-27503-5 (CoreCLR 4.6.27422.72, CoreFX 4.7.19.12807), 64bit RyuJIT
DefaultJob : .NET Core 3.0.0-preview3-27503-5 (CoreCLR 4.6.27422.72, CoreFX 4.7.19.12807), 64bit RyuJIT
Conclusion: |
OK, thanks I"ll mark this as up for grabs in case someone wants to look further. cc @GrabYourPitchforks as he's an expert in using XOR with checking ASCII ranges... 😈 |
As @stephentoub said, we made substantial performance improvements to this for 3.0. The reference sources referred to are for Full Framework 4.7.2 and aren't representative of what's currently in Core. See dotnet/coreclr@dd6c690 for the current code in Core. (This commit is for I'm not quite sure what specifically this issue is asking for. Can somebody clarify? |
@GrabYourPitchforks Well I finally found the core 3.0 code thanks to your link. The XOR-variant was removed and replace by code that is faster I assume. So it seems someone had the same idea as I had, but before me and than someone else even found a better way. I close this issue.... Thanx for the support. |
@thargol1 thanks for the suggestion though. We would welcome contributions in other areas, if you decide you'd like to become more famliar with Github and the .NET codebase. Just look for issues tagged "easy" or "up-for-grabs".. |
I also strongly recommend checking out the https://source.dot.net/ tool as an easy way to browse up-to-date Core sources. It's updated every few days. Type String.StartsWith into the upper-left search field on that site and you can browse the sources, even being able to click through to other methods that the higher-level method calls into. |
JIT: Improve performance on String.StartsWith(constant string)
I like using String.StartsWith because it's self explanatory and make code easy to read.
How ever on some occasions I ran into performance issues and had to change my code, to faster less readable code.
I created an example and a benchmark. The code with char compare is over 20 times faster.
Can't the JIT-compiler to this kind of optimisation for me?
Benchmark results:
The text was updated successfully, but these errors were encountered: