What are these extra disassembly instructions when using SIMD intrinsics?

[亡魂溺海] 提交于 2019-12-01 04:45:48

I'll annotate the code generation that I see, for a processor that supports AVX2 like Haswell, it can move 8 floats at a time:

00007FFA1ECD4E20  push        rsi
00007FFA1ECD4E21  sub         rsp,20h  

00007FFA1ECD4E25  xor         eax,eax                       ; i = 0
00007FFA1ECD4E27  mov         r8d,dword ptr [rcx+8]         ; a.Length
00007FFA1ECD4E2B  lea         r9d,[r8-8]                    ; a.Length - simdLength
00007FFA1ECD4E2F  test        r9d,r9d                       ; if (i >= a.Length - simdLength)
00007FFA1ECD4E32  jle         00007FFA1ECD4E75              ; then skip loop 

00007FFA1ECD4E34  mov         r10d,dword ptr [rdx+8]        ; b.Length
00007FFA1ECD4E38  cmp         eax,r8d                       ; if (i >= a.Length)
00007FFA1ECD4E3B  jae         00007FFA1ECD4E7B              ; then OutOfRangeException
00007FFA1ECD4E3D  lea         r11d,[rax+7]                  ; i+7
00007FFA1ECD4E41  cmp         r11d,r8d                      ; if (i+7 >= a.Length)
00007FFA1ECD4E44  jae         00007FFA1ECD4E7B              ; then OutOfRangeException

00007FFA1ECD4E46  mov         rsi,rcx                       ; move a[i..i+7]
00007FFA1ECD4E49  vmovupd     ymm0,ymmword ptr [rsi+rax*4+10h]  

00007FFA1ECD4E50  cmp         eax,r10d                      ; same as above 
00007FFA1ECD4E53  jae         00007FFA1ECD4E7B              ; but for b
00007FFA1ECD4E55  cmp         r11d,r10d  
00007FFA1ECD4E58  jae         00007FFA1ECD4E7B  
00007FFA1ECD4E5A  vmovupd     ymm1,ymmword ptr [rdx+rax*4+10h]  

00007FFA1ECD4E61  vaddps      ymm0,ymm0,ymm1                ; a[i..] + b[i...]
00007FFA1ECD4E66  vmovupd     ymmword ptr [rsi+rax*4+10h],ymm0  

00007FFA1ECD4E6D  add         eax,8                         ; i += 8
00007FFA1ECD4E70  cmp         r9d,eax                       ; if (i < a.Length)
00007FFA1ECD4E73  jg          00007FFA1ECD4E38              ; then loop

00007FFA1ECD4E75  add         rsp,20h  
00007FFA1ECD4E79  pop         rsi  
00007FFA1ECD4E7A  ret  

So the eax compares are those "pesky bound checks" that the blog post talks about. The blog post gives an optimized version that is not actually implemented (yet), real code right now checks both the first and the last index of the 8 floats that are moved at the same time. The blog post's comment "Hopefully, we'll get our bounds-check elimination work strengthened enough" is an uncompleted task :)

The mov rsi,rcx instruction is present in the blog post as well and appears to be a limitation in the register allocator. Probably influenced by RCX being an important register, it normally stores this. Not important enough to do the work to get this optimized away I'd assume, register-to-register moves take 0 cycles since they only affect register renaming.

Note how the difference between SSE2 and AVX2 is ugly, while the code moves and adds 8 floats at a time, it only actually uses 4 of them. Vector<float>.Count is 4 regardless of the processor flavor, leaving 2x perf on the table. Hard to hide the implementation detail I guess.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!