我认识到Clang(10.0)和MSVC(16.7)生成的汇编程序具有非常不同的性能(对于Clang是〜3.3ns
,对于MSVC是〜8ns
)是通过同一块C ++代码和一些AVX2内在函数。
代码正在计算可以从给定整数(10
中分解出的rcx
的最大指数,并且在分解出10
的最大幂后也计算商
为弄清代码的作用,它计算(以10为底)给定整数的尾随零,并计算通过除去这些尾随零而获得的数字。例如,当输入为63700
时,输出为{637, 2}
,当输入为1230000
时,输出为{123, 4}
。
以下是可以重现代码的{godbolt}链接:https://godbolt.org/z/5xoK3G
在231行中,您可以选择一个使用lambda(生成vmovups
,重新加载2个标量存储并复制)和不使用lambda(不生成那些{{1} }。
要弄清楚原因,我做了几次测试。事实证明,差异来自以下事实:MSVC版本使用两个vmovups
(一个用于加载,一个用于存储)写回结果,而Clang使用两个vmovups
(两个都对于商店)。
为证实这一假设,我做了几件事来强制MSVC生成mov
而不是mov
,并且所生成的代码与Clang生成的代码几乎相同。
问题是,为什么在这种情况下使用vmovups
(或其他类似的矢量移动指令)会大大降低性能?
我附加了两个由MSVC生成的程序集输出,一个带有vmovups
(因此执行〜{vmovups
),另一个不带有8ns
(因此执行〜vmovups
):>
使用3.3ns
:(此版本还包含/减少了堆栈指针;是否有必要?)
vmovups
没有00007FF7227114B0 sub rsp,18h
00007FF7227114B4 mov r8,rcx
00007FF7227114B7 lea rcx,[divtest_table_holder<unsigned int,5,9>::table (07FF7227133B0h)]
00007FF7227114BE vmovd xmm0,r8d
00007FF7227114C3 vpbroadcastd ymm0,xmm0
00007FF7227114C8 vpmulld ymm1,ymm0,ymmword ptr [divtest_table_holder<unsigned int,5,9>::table+4h (07FF7227133B4h)]
00007FF7227114D1 vpminud ymm0,ymm1,ymmword ptr [divtest_table_holder<unsigned int,5,9>::table+28h (07FF7227133D8h)]
00007FF7227114DA vpcmpeqd ymm1,ymm0,ymm1
00007FF7227114DE vpmovmskb eax,ymm1
00007FF7227114E2 popcnt edx,eax
00007FF7227114E6 tzcnt eax,r8d
00007FF7227114EB shr edx,2
00007FF7227114EE cmp eax,edx
00007FF7227114F0 cmovl edx,eax
00007FF7227114F3 movsxd rax,edx
00007FF7227114F6 mov dword ptr [rsp+8],edx
00007FF7227114FA mov ecx,dword ptr [rcx+rax*4]
00007FF7227114FD imul rcx,r8
00007FF722711501 mov eax,edx
00007FF722711503 shrx rcx,rcx,rax
00007FF722711508 mov qword ptr [rsp],rcx
00007FF72271150C vmovups xmm0,xmmword ptr [rsp]
00007FF722711511 vmovups xmmword ptr [rsp],xmm0
00007FF722711516 vzeroupper
00007FF722711519 add rsp,18h
00007FF72271151D ret
:
vmovups
此外,因为这是我编写的第一个SIMD代码,所以如果我做错了任何事情,我将不胜感激。