Uh oh!
There was an error while loading. Please reload this page.
Handle final elements in SpanHelpers.Contains for byte and char vectorized - #67492
Conversation
ghost
commented
Apr 2, 2022
Tagging subscribers to this area: @dotnet/area-system-memory Issue DetailsDescriptionLet's assume we have a In numbers for byte, AVX2, n = 2, and k = 1: So as ratio there are The proposed change avoids the sequential processing of the remaining elements by reading a final vector from the end of the (Note: the same / similar approach is used in #67049, and some other places where idempotency can be used (I commented quite a few times on this 😉)) Benchmark resultsNotes
For the benchmarks the machine infoBenchmarkDotNet=v0.13.1, OS=Windows 10.0.19043.1586 (21H1/May2021Update)
Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
.NET SDK=7.0.100-preview.4.22181.7
[Host] : .NET 7.0.0 (7.0.22.17907), X64 RyuJIT
DefaultJob : .NET 7.0.0 (7.0.22.17907), X64 RyuJITbool Contains(ref byte searchSpace, byte value, int length)bool Contains(ref char searchSpace, char value, int length)Machine code (x64)SpanHelpers.Contains(byte); SpanHelpersContainsByteBenchmark.Default()movrdx,[rcx+8]movzxeax,byte ptr [rcx+14]movr8d,[rcx+10]movrcx,rdxmovedx,eaxjmp qword ptr [7FFB35F51420]; Total bytes of code 23; SpanHelpersContainsByteBenchmark.Contains(Byte ByRef, Byte, Int32)vzerouppermovzxeax,dlmovedx,eaxxorr9d,r9dmovr10d,r8dmovr11,r10cmpr8d,40jl short M01_L00movr11,rcxandr11,1Fnegr11addr11,20andr11,1FM01_L00:cmpr11,8jb near ptr M01_L02M01_L01:addr11,0FFFFFFFFFFFFFFF8movzxr8d,byte ptr [rcx+r9]cmpr8d,edxje near ptr M01_L09movzxr8d,byte ptr [rcx+r9+1]cmpr8d,edxje near ptr M01_L09movzxr8d,byte ptr [rcx+r9+2]cmpr8d,edxje near ptr M01_L09movzxr8d,byte ptr [rcx+r9+3]cmpr8d,edxje near ptr M01_L09movzxr8d,byte ptr [rcx+r9+4]cmpr8d,edxje near ptr M01_L09movzxr8d,byte ptr [rcx+r9+5]cmpr8d,edxje near ptr M01_L09movzxr8d,byte ptr [rcx+r9+6]cmpr8d,edxje near ptr M01_L09movzxr8d,byte ptr [rcx+r9+7]cmpr8d,edxje near ptr M01_L09addr9,8cmpr11,8jae near ptr M01_L01M01_L02:cmpr11,4jb short M01_L03addr11,0FFFFFFFFFFFFFFFCmovzxr8d,byte ptr [rcx+r9]cmpr8d,edxje near ptr M01_L09movzxr8d,byte ptr [rcx+r9+1]cmpr8d,edxje near ptr M01_L09movzxr8d,byte ptr [rcx+r9+2]cmpr8d,edxje near ptr M01_L09movzxr8d,byte ptr [rcx+r9+3]cmpr8d,edxje near ptr M01_L09addr9,4M01_L03:testr11,r11je short M01_L05M01_L04:decr11movzxr8d,byte ptr [rcx+r9]cmpr8d,edxje short M01_L09incr9testr11,r11jne short M01_L04M01_L05:cmpr9,r10jae short M01_L08movr11,r10subr11,r9andr11,0FFFFFFFFFFFFFFE0imulr8d,eax,1010101 vmovd xmm0,r8dvpbroadcastdymm0,xmm0cmpr11,r9jbe short M01_L07nop dword ptr [rax]nop dword ptr [rax+rax]M01_L06: vpcmpeqb ymm1,ymm0,[rcx+r9] vxorps ymm2,ymm2,ymm2vpxorymm1,ymm2,ymm1vptestymm1,ymm1jne short M01_L09addr9,20cmpr11,r9ja short M01_L06M01_L07:cmpr9,r10jae short M01_L08movr11,r10subr11,r9jmp near ptr M01_L00M01_L08:xoreax,eaxvzeroupperretM01_L09:moveax,1vzeroupperret; Total bytes of code 397; SpanHelpersContainsByteBenchmark.PR()movrdx,[rcx+8]movzxeax,byte ptr [rcx+14]movr8d,[rcx+10]movrcx,rdxmovedx,eaxjmp qword ptr [7FFB35F61438]; Total bytes of code 23; SpanHelpersContainsByteBenchmark.Contains_PR(Byte ByRef, Byte, Int32)pushrdipushrsivzerouppermovzxeax,dlmovedx,eaxxorr9d,r9dmovr10d,r8dmovr11,r10cmpr8d,40jl short M01_L00movr11,rcxandr11,1Fnegr11addr11,20andr11,1FM01_L00:cmpr11,8jb short M01_L02nop dword ptr [rax]M01_L01:addr11,0FFFFFFFFFFFFFFF8learsi,[rcx+r9]movzxedi,byte ptr [rsi]cmpedx,edije near ptr M01_L09movzxedi,byte ptr [rsi+1]cmpedx,edije near ptr M01_L09movzxedi,byte ptr [rsi+2]cmpedx,edije near ptr M01_L09movzxedi,byte ptr [rsi+3]cmpedx,edije near ptr M01_L09movzxedi,byte ptr [rsi+4]cmpedx,edije near ptr M01_L09movzxedi,byte ptr [rsi+5]cmpedx,edije near ptr M01_L09movzxedi,byte ptr [rsi+6]cmpedx,edije near ptr M01_L09movzxesi,byte ptr [rsi+7]cmpedx,esije near ptr M01_L09addr9,8cmpr11,8jae short M01_L01M01_L02:cmpr11,4jb short M01_L03addr11,0FFFFFFFFFFFFFFFClearsi,[rcx+r9]movzxedi,byte ptr [rsi]cmpedx,edije near ptr M01_L09movzxedi,byte ptr [rsi+1]cmpedx,edije near ptr M01_L09movzxedi,byte ptr [rsi+2]cmpedx,edije near ptr M01_L09movzxesi,byte ptr [rsi+3]cmpedx,esije near ptr M01_L09addr9,4M01_L03:testr11,r11je short M01_L05nop dword ptr [rax+rax]M01_L04:decr11movzxesi,byte ptr [rcx+r9]cmpesi,edxje short M01_L09incr9testr11,r11jne short M01_L04M01_L05:cmpr9,r10jae short M01_L08movr11,r10subr11,r9andr11,0FFFFFFFFFFFFFFE0 vxorps ymm0,ymm0,ymm0imuleax,1010101 vmovd xmm1,eaxvpbroadcastdymm1,xmm1cmpr9,r11jae short M01_L07M01_L06: vpcmpeqb ymm2,ymm1,[rcx+r9]vpxorymm2,ymm0,ymm2vptestymm2,ymm2jne short M01_L09addr9,20cmpr9,r11jb short M01_L06M01_L07:cmpr9,r10jae short M01_L08addr8d,0FFFFFFE0movr9d,r8d vpcmpeqb ymm2,ymm1,[rcx+r9]vpxorymm0,ymm0,ymm2vptestymm0,ymm0jne short M01_L09M01_L08:xoreax,eaxvzeroupperpoprsipoprdiretM01_L09:moveax,1vzeroupperpoprsipoprdiret; Total bytes of code 389SpanHelpers.Contains(char); SpanHelpersContainsCharBenchmark.Default()movrdx,[rcx+8]movzxeax,word ptr [rcx+14]movr8d,[rcx+10]movrcx,rdxmovedx,eaxjmp qword ptr [7FFB35F51420]; Total bytes of code 23; SpanHelpersContainsCharBenchmark.Contains(Char ByRef, Char, Int32)pushraxvzeroupperxoreax,eaxmov[rsp],raxmov[rsp],rcxmovsxdrax,r8dlear9,[rcx+rax*2]cmpr8d,20jl short M01_L00movr8d,ecxandr8d,1Fmoveax,r8dshreax,1Faddeax,r8dsareax,1movr8d,eaxnegr8daddr8d,10andr8d,0FM01_L00:cmpr8d,4jl short M01_L02movzxr10d,dxM01_L01:addr8d,0FFFFFFFCmovzxeax,word ptr [rcx]cmpr10d,eaxje near ptr M01_L08movzxeax,word ptr [rcx+2]cmpr10d,eaxje near ptr M01_L08movzxeax,word ptr [rcx+4]cmpr10d,eaxje near ptr M01_L08movzxeax,word ptr [rcx+6]cmpr10d,eaxje near ptr M01_L08addrcx,8cmpr8d,4jge short M01_L01M01_L02:testr8d,r8djle short M01_L04movzxr10d,dxnopM01_L03:decr8dmovzxeax,word ptr [rcx]cmpr10d,eaxje near ptr M01_L08addrcx,2testr8d,r8djg short M01_L03M01_L04:cmprcx,r9jae short M01_L07movr8,r9subr8,rcxmovrax,r8shrrax,3Faddrax,r8sarrax,1movr8d,eaxandr8d,0FFFFFFF0movzxr10d,dximuleax,r10d,10001 vmovd xmm0,eaxvpbroadcastdymm0,xmm0testr8d,r8djle short M01_L06M01_L05: vpcmpeqw ymm1,ymm0,[rcx] vxorps ymm2,ymm2,ymm2vpxorymm1,ymm2,ymm1vptestymm1,ymm1jne short M01_L08addrcx,20addr8d,0FFFFFFF0testr8d,r8djg short M01_L05M01_L06:cmprcx,r9jae short M01_L07movr8,r9subr8,rcxmovrax,r8shrrax,3Faddrax,r8sarrax,1movr8d,eaxjmp near ptr M01_L00M01_L07:xoreax,eaxvzeroupperaddrsp,8retM01_L08:moveax,1vzeroupperaddrsp,8ret; Total bytes of code 311; SpanHelpersContainsCharBenchmark.PR()movrdx,[rcx+8]movzxeax,word ptr [rcx+14]movr8d,[rcx+10]movrcx,rdxmovedx,eaxjmp qword ptr [7FFB35F51438]; Total bytes of code 23; SpanHelpersContainsCharBenchmark.Contains_PR(Char ByRef, Char, Int32)pushrsisubrsp,10vzeroupperxoreax,eaxmov[rsp+8],raxmov[rsp+8],rcxxorr9d,r9dmovr10d,r8dmovr11,r10cmpr8d,20jl short M01_L00movr11d,ecxandr11d,1Fshrr11d,1moveax,r11dnegeaxaddeax,10andeax,0Fmovr11d,eaxM01_L00:cmpr11,4jb short M01_L02movzxr8d,dxM01_L01:addr11,0FFFFFFFFFFFFFFFClearax,[rcx+r9*2]movzxesi,word ptr [rax]cmpr8d,esije near ptr M01_L08movzxesi,word ptr [rax+2]cmpr8d,esije near ptr M01_L08movzxesi,word ptr [rax+4]cmpr8d,esije near ptr M01_L08movzxeax,word ptr [rax+6]cmpr8d,eaxje near ptr M01_L08addr9,4cmpr11,4jae short M01_L01M01_L02:testr11,r11je short M01_L04movzxr8d,dxnop dword ptr [rax+rax]nop dword ptr [rax+rax]M01_L03:decr11movzxeax,word ptr [rcx+r9*2]cmpeax,r8dje near ptr M01_L08incr9testr11,r11jne short M01_L03M01_L04:cmpr9,r10jae short M01_L07movr11,r10subr11,r9andr11,0FFFFFFFFFFFFFFF0 vxorps ymm0,ymm0,ymm0movzxr8d,dximuleax,r8d,10001 vmovd xmm1,eaxvpbroadcastdymm1,xmm1cmpr9,r11jae short M01_L06nop word ptr [rax+rax]M01_L05: vpcmpeqw ymm2,ymm1,[rcx+r9*2]vpxorymm2,ymm0,ymm2vptestymm2,ymm2jne short M01_L08addr9,10cmpr9,r11jb short M01_L05M01_L06:cmpr9,r10jae short M01_L07 vpcmpeqw ymm2,ymm1,[rcx+r10*2+0FFE0]vpxorymm0,ymm0,ymm2vptestymm0,ymm0jne short M01_L08M01_L07:xoreax,eaxvzeroupperaddrsp,10poprsiretM01_L08:moveax,1vzeroupperaddrsp,10poprsiret; Total bytes of code 314👉 If this looks good, I'd like to look into IndexOf, etc. too.
|
This comment was marked as outdated.
This comment was marked as outdated.
danmoseley
commented
Apr 3, 2022
It seems we're missing benchmarks for this? (If so can we add yours?) |
VS didn't do this for comments (at least in my setup) automatically :-(
gfoidl
commented
Apr 3, 2022
Sure 😃 dotnet/performance#2347 |
Uh oh!
There was an error while loading. Please reload this page.
| var matches = Vector.Equals(values, LoadVector(ref searchSpace, offset)); | ||
| if (Vector<byte>.Zero.Equals(matches)) | ||
| matches = Vector.Equals(values, LoadVector(ref searchSpace, offset)); | ||
| if (zero.Equals(matches)) |
There was a problem hiding this comment.
More over you don't need to hoist it - it should not be used
There was a problem hiding this comment.
Ah, vec1 == vec2 emits better code.
For the char-overload:
M01_L05:
vpcmpeqw ymm2,ymm1,[rcx+r9*2]
- vpxor ymm2,ymm0,ymm2
vptest ymm2,ymm2
jne short M01_L08
add r9,10
cmp r9,r11
jb short M01_L05
M01_L06:
cmp r9,r10
jae short M01_L07
vpcmpeqw ymm2,ymm1,[rcx+r10*2+0FFE0]
- vpxor ymm0,ymm0,ymm2
vptest ymm0,ymm0
jne short M01_L08Thanks for the hint!
Will create an issue for that --> #67500
| lengthToExamine = UnalignedCountVector(ref searchSpace); | ||
| } | ||
| SequentialScan: |
There was a problem hiding this comment.
a removal of anything goto-related is always welcomed 👍
| } | ||
| if (offset < (nuint)(uint)length) | ||
| // The total length is at least Vector<byte>.Count, so instead of falling back to a |
There was a problem hiding this comment.
thank you for adding the comment (otherwise it would not be obvious to me) 👍
Did we expect any perf regressions from this? Seems like it might be related to dotnet/perf-autofiling-issues#4884 |
gfoidl
commented
Apr 30, 2022
No regression is expected, rather it should be an improvement. When I check the benchmark-code What is a proper way to investigate this regression? |


Description
Let's assume we have a
searchSpaceof length(n + 1) * Vector<T>.Count - k, whereTis eitherbyteorchar, andk in (0, Vector<T>.Count).So current implementation -- ignoring alignment for a moment -- can perform
nvectorized operations, then falls back to sequential processing of the remainingVector<T>.Count - kelements.In numbers for byte, AVX2, n = 2, and k = 1:
So as ratio there are
(Vector<T>.Count - k) / (n * Vector<T>.Count)elements that need to processed sequential.The worst case is for
k = 1and smalln, i.e. for AVX2 andk = 131 elements need to be processed sequential.The proposed change avoids the sequential processing of the remaining elements by reading a final vector from the end of the
searchSpace.When exiting the standard vectorized loop, we know that the
searchSpaceis at leastVector<T>.Countlong, so it is safe to read from that end, and the operation is idempotent too.Thus in total we do
n + 1vectorized operations.(Note: the same / similar approach is used in #67049, and some other places where idempotency can be used (I commented quite a few times on this 😉))
Benchmark results
Notes
Vector<T>.Zerooutside the loop, this is done manually with this PR and that's why for length 64 (byte) and 32 (char) a speedup is shownFor the benchmarks the
searchSpaceis aligned to 32 bytes, to have reproducable results.machine info
bool Contains(ref byte searchSpace, byte value, int length)
bool Contains(ref char searchSpace, char value, int length)
Machine code (x64)
SpanHelpers.Contains(byte)
SpanHelpers.Contains(char)
👉 If this looks good, I'd like to look into IndexOf, etc. too.