Skip to content

Improve vectorization of IndexOf(chars, StringComparison.OrdinalIgnoreCase) - #85437

Merged
stephentoub merged 3 commits into
dotnet:mainfrom
stephentoub:vectorordinalignorecase
May 1, 2023
Merged

Improve vectorization of IndexOf(chars, StringComparison.OrdinalIgnoreCase)#85437
stephentoub merged 3 commits into
dotnet:mainfrom
stephentoub:vectorordinalignorecase

Conversation

@stephentoub

@stephentoubstephentoub commented Apr 27, 2023

Copy link
Copy Markdown
Member

Use the same general "Algorithm 1: Generic SIMD" that we do for StringComparison.Ordinal, adapted for OrdinalIgnoreCase.

privatestaticreadonlystrings_haystack=newHttpClient().GetStringAsync("https://www.gutenberg.org/files/1661/1661-0.txt").Result;[Params("watson","elementary","holmes","the")]publicstringNeedle{get;set;}[Benchmark]publicintCount(){intcount=0;ReadOnlySpan<char>haystack=s_haystack;while(true){intpos=haystack.IndexOf(Needle,StringComparison.OrdinalIgnoreCase);if(pos<0)break;count++;haystack=haystack.Slice(pos+Needle.Length);}returncount;}
MethodToolchainNeedleMeanErrorStdDevRatio
Count\main\corerun.exeelementary580.54 us3.562 us2.781 us1.00
Count\pr\corerun.exeelementary59.37 us0.447 us0.397 us0.10
Count\main\corerun.exeholmes366.58 us0.607 us0.568 us1.00
Count\pr\corerun.exeholmes82.55 us0.204 us0.181 us0.23
Count\main\corerun.exethe547.91 us1.257 us1.050 us1.00
Count\pr\corerun.exethe258.62 us1.123 us0.996 us0.47
Count\main\corerun.exewatson230.76 us0.550 us0.514 us1.00
Count\pr\corerun.exewatson58.24 us0.448 us0.419 us0.25

@stephentoubstephentoub added this to the 8.0.0 milestone Apr 27, 2023
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/area-system-runtime
See info in area-owners.md if you want to be subscribed.

Issue Details

Use the same general "Algorithm 1: Generic SIMD" that we do for StringComparison.Ordinal, adapter for OrdinalIgnoreCase.

[Params("watson","elementary","holmes","the")]publicstringNeedle{get;set;}[Benchmark]publicintCount(){intcount=0;ReadOnlySpan<char>haystack=s_haystack;while(true){intpos=haystack.IndexOf(Needle,StringComparison.OrdinalIgnoreCase);if(pos<0)break;count++;haystack=haystack.Slice(pos+Needle.Length);}returncount;}
MethodToolchainNeedleMeanErrorStdDevRatio
Count\main\corerun.exeelementary580.54 us3.562 us2.781 us1.00
Count\pr\corerun.exeelementary59.37 us0.447 us0.397 us0.10
Count\main\corerun.exeholmes366.58 us0.607 us0.568 us1.00
Count\pr\corerun.exeholmes82.55 us0.204 us0.181 us0.23
Count\main\corerun.exethe547.91 us1.257 us1.050 us1.00
Count\pr\corerun.exethe258.62 us1.123 us0.996 us0.47
Count\main\corerun.exewatson230.76 us0.550 us0.514 us1.00
Count\pr\corerun.exewatson58.24 us0.448 us0.419 us0.25
Author:stephentoub
Assignees:-
Labels:

area-System.Runtime, tenet-performance

Milestone:8.0.0

…eCase)
Use the same general "Algorithm 1: Generic SIMD" that we do for StringComparison.Ordinal, adapter for OrdinalIgnoreCase.
@stephentoub
stephentoubforce-pushed the vectorordinalignorecase branch from 3fb61ee to fe53637CompareApril 27, 2023 20:29
// Load a vector from the current search space offset and another from the offset plus the distance between the two characters.
// For each, | with 0x20 so that letters are lowercased, then & those together to get a mask. If the mask is all zeros, there
// was no match. If it wasn't, we have to do more work to check for a match.
Vector128<ushort> cmpCh2 = Vector128.Equals(ch2, Vector128.BitwiseOr(Vector128.LoadUnsafe(ref searchSpace, (nuint)(offset + ch1ch2Distance)), Vector128.Create((ushort)0x20)));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nit: Vector128.BitwiseOr -> |

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just style, right? Happy to change it, just questioning whether it's worth rerunning ci.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

definitely not worth it 🙂

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right its "just style". There is also the general considerations of "methods" vs "operators" (such as precedence and readability) but we're not super consistent today just due to the operators being relatively new.

@EgorBoEgorBo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Assuming we're fine with the overhead for the worst case - it's slightly bigger in case of OrdinalIgnoreCase due to more work + the path to find unique chars is more expensive, but the benefits should outweight that 👍

@stephentoub

Copy link
Copy Markdown
MemberAuthor

I think it's worth it. It's more expensive to set up than ordinal, but the match validation that happens on every potential match is also more expensive, and this generally lessens the latter. It will regress in cases similar to ordinal regressed, eg where the starting character never matches, but on the balance I expect it'll be a meaningful win. Let's try and see what falls out. :-)

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Runtimetenet-performancePerformance related issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@stephentoub@EgorBo@tannergooding