使用SIMD内在函数时,这些额外的反汇编指令是什么?

我正在测试通过使用RyuJIT的SIMD指令可以得到什么样的加速,我看到一些我不期望的反汇编指令。 我将这段代码基于RyuJIT团队的Kevin Frei的博客文章以及相关post。 这是function:

static void AddPointwiseSimd(float[] a, float[] b) { int simdLength = Vector.Count; int i = 0; for (i = 0; i < a.Length - simdLength; i += simdLength) { Vector va = new Vector(a, i); Vector vb = new Vector(b, i); va += vb; va.CopyTo(a, i); } } 

我正在查询的反汇编部分将数组值复制到Vector 。 大多数反汇编与Kevin和Sasha的post类似,但我强调了一些额外的指令(以及我的混淆注释),这些指令没有出现在他们的反汇编中:

 ;// Vector va = new Vector(a, i); cmp eax,r8d ; <-- Unexpected - Compare a.Length to i? jae 00007FFB17DB6D5F ; <-- Unexpected - Jump to range check failure lea r10d,[rax+3] cmp r10d,r8d jae 00007FFB17DB6D5F mov r11,rcx ; <-- Unexpected - Extra register copy? movups xmm0,xmmword ptr [r11+rax*4+10h ] ;// Vector vb = new Vector(b, i); cmp eax,r9d ; <-- Unexpected - Compare b.Length to i? jae 00007FFB17DB6D5F ; <-- Unexpected - Jump to range check failure cmp r10d,r9d jae 00007FFB17DB6D5F movups xmm1,xmmword ptr [rdx+rax*4+10h] 

请注意循环范围检查是否符合预期:

 ;// for (i = 0; i < a.Length - simdLength; i += simdLength) { add eax,4 cmp r9d,eax jg loop 

所以我不知道为什么有额外的eax比较。 任何人都可以解释为什么我看到这些额外的指令,如果有可能摆脱它们。

如果它与项目设置有关,我有一个非常相似的项目, 在github上显示相同的问题(参见FloatSimdProcessor.HwAcceleratedSumInPlace()UShortSimdProcessor.HwAcceleratedSumInPlaceUnchecked() )。

我会注释我看到的代码生成,对于支持像Haswell这样的AVX2的处理器,它一次可以移动8个浮点数:

 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 

所以eax比较是博客文章谈到的那些“讨厌的约束检查”。 博客文章给出了一个尚未实际实现的优化版本,实际代码现在检查同时移动的8个浮动的第一个和最后一个索引。 博客文章的评论“希望,我们将得到足够强化我们的边界检查淘汰工作”是一项未完成的任务:)

mov rsi,rcx指令也出现在博客文章中,似乎是寄存器分配器的限制。 可能受到RCX作为重要寄存器的影响,它通常会存储 。 我认为,寄存器到寄存器的移动只需要0个周期,因为它们只影响寄存器重命名,因此不够重要。

注意SSE2和AVX2之间的区别是如何丑陋的,而代码移动并一次添加8个浮点数,它实际上只使用了4个浮点数。 无论处理器的味道如何, Vector.Count为4,在桌面上留下2x perf。 我猜,很难隐藏实现细节。