使用 SIMD 内部函数时这些额外的反汇编指令是什么?
What are these extra disassembly instructions when using SIMD intrinsics?
我正在测试通过 RyuJIT 使用 SIMD 指令可以获得什么样的加速,并且我看到了一些我不期望的反汇编指令。我的代码基于 this blog post from the RyuJIT team's Kevin Frei, and a related post here。这是函数:
static void AddPointwiseSimd(float[] a, float[] b) {
int simdLength = Vector<float>.Count;
int i = 0;
for (i = 0; i < a.Length - simdLength; i += simdLength) {
Vector<float> va = new Vector<float>(a, i);
Vector<float> vb = new Vector<float>(b, i);
va += vb;
va.CopyTo(a, i);
}
}
我正在查询的反汇编部分将数组值复制到 Vector<float>
。大多数反汇编与 Kevin 和 Sasha 的帖子中的类似,但我突出显示了一些未出现在他们的反汇编中的额外说明(以及我混淆的注释):
;// Vector<float> va = new Vector<float>(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<float> vb = new Vector<float>(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
。谁能解释为什么我会看到这些额外的说明,以及是否有可能摆脱它们。
如果它与项目设置有关,我有一个非常相似的项目显示相同的问题here on 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 比较的是博客 post 谈论的那些 "pesky bound checks"。博客 post 给出了一个未实际实现(尚未)的优化版本,实际代码现在检查同时移动的 8 个浮点数的第一个和最后一个索引。博客post的评论"Hopefully, we'll get our bounds-check elimination work strengthened enough"是未完成的任务:)
mov rsi,rcx
指令也出现在博客 post 中,并且似乎是寄存器分配器中的一个限制。可能受RCX是一个重要的寄存器的影响,它通常存储this。不够重要,不足以完成我假设的优化工作,register-to-register 移动需要 0 个周期,因为它们只影响寄存器重命名。
请注意 SSE2 和 AVX2 之间的区别是多么丑陋,虽然代码移动并一次添加 8 个浮点数,但实际上只使用了其中的 4 个。 Vector<float>.Count
是 4,无论处理器风格如何,在 table 上留下 2x perf。我想很难隐藏实现细节。
我正在测试通过 RyuJIT 使用 SIMD 指令可以获得什么样的加速,并且我看到了一些我不期望的反汇编指令。我的代码基于 this blog post from the RyuJIT team's Kevin Frei, and a related post here。这是函数:
static void AddPointwiseSimd(float[] a, float[] b) {
int simdLength = Vector<float>.Count;
int i = 0;
for (i = 0; i < a.Length - simdLength; i += simdLength) {
Vector<float> va = new Vector<float>(a, i);
Vector<float> vb = new Vector<float>(b, i);
va += vb;
va.CopyTo(a, i);
}
}
我正在查询的反汇编部分将数组值复制到 Vector<float>
。大多数反汇编与 Kevin 和 Sasha 的帖子中的类似,但我突出显示了一些未出现在他们的反汇编中的额外说明(以及我混淆的注释):
;// Vector<float> va = new Vector<float>(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<float> vb = new Vector<float>(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
。谁能解释为什么我会看到这些额外的说明,以及是否有可能摆脱它们。
如果它与项目设置有关,我有一个非常相似的项目显示相同的问题here on 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 比较的是博客 post 谈论的那些 "pesky bound checks"。博客 post 给出了一个未实际实现(尚未)的优化版本,实际代码现在检查同时移动的 8 个浮点数的第一个和最后一个索引。博客post的评论"Hopefully, we'll get our bounds-check elimination work strengthened enough"是未完成的任务:)
mov rsi,rcx
指令也出现在博客 post 中,并且似乎是寄存器分配器中的一个限制。可能受RCX是一个重要的寄存器的影响,它通常存储this。不够重要,不足以完成我假设的优化工作,register-to-register 移动需要 0 个周期,因为它们只影响寄存器重命名。
请注意 SSE2 和 AVX2 之间的区别是多么丑陋,虽然代码移动并一次添加 8 个浮点数,但实际上只使用了其中的 4 个。 Vector<float>.Count
是 4,无论处理器风格如何,在 table 上留下 2x perf。我想很难隐藏实现细节。