Gcc 在x64模式下为_mm_movemask_epi8内部生成不必要的指令
来自SSE2的固有函数Gcc 在x64模式下为_mm_movemask_epi8内部生成不必要的指令,gcc,64-bit,x86-64,sse,micro-optimization,Gcc,64 Bit,X86 64,Sse,Micro Optimization,来自SSE2的固有函数\u mm\u movemask\u epi8由Intel使用以下原型定义: int _mm_movemask_epi8 (__m128i a); 此内在函数直接对应于所有编译器生成的pmovmskb指令 根据,在x64模式下,pmovmskb指令可以将生成的整数掩码写入32位或64位通用寄存器。在任何情况下,只有结果的16个低位可以为非零,即结果肯定在范围[0;65535]内 说到内在函数\u mm\u movemask\u epi8,其返回值的类型为int,在大多
\u mm\u movemask\u epi8
由Intel使用以下原型定义:
int _mm_movemask_epi8 (__m128i a);
此内在函数直接对应于所有编译器生成的pmovmskb
指令
根据,在x64模式下,pmovmskb
指令可以将生成的整数掩码写入32位或64位通用寄存器。在任何情况下,只有结果的16个低位可以为非零,即结果肯定在范围[0;65535]内
说到内在函数\u mm\u movemask\u epi8
,其返回值的类型为int
,在大多数平台上它是32位大小的有符号整数。不幸的是,在x64模式下没有其他函数返回64位整数。因此:
pmovmskb
指令(例如eax
)rax
)为零mov eax,eax
)将64位寄存器的上半部分归零,因为该寄存器后来用作64位值(例如,作为数组的索引)x=array[_mm_movemask_epi8(xmmValue)]中代码>
mov-eax,eax
)的大致成本是多少?这些指令是否有可能被CPU内部完全消除,并且它们实际上不占用执行单元的时间(Agner Fog的指令表文档提到了这种可能性)#包括
#包括
int32测试32(常数m128i v){
int32_t mask=_mm_movemask_epi8(v);
返回掩码;
}
int64测试64(常数m128i v){
int64_t mask=_mm_movemask_epi8(v);
返回掩码;
}
生成:
test32(长向量(2)):#@test32(长向量(2))
vpmovmskb eax,xmm0
ret
test64(长向量(2)):#@test64(长向量(2))
vpmovmskb eax,xmm0
ret
鉴于在64位情况下生成额外的cdqe
指令:
test32(长向量(2)):
vpmovmskb eax,xmm0
ret
test64(长向量(2)):
vpmovmskb eax,xmm0
cdqe
ret
为什么会这样
gcc的内部指令定义告诉它pmovmskb
做什么,但它一定没有通知它rax
的上32位始终为零。我的猜测是,它被视为函数调用返回值,ABI允许返回32位int的函数在rax
的上32位留下垃圾
GCC确实知道一般零扩展中的32位操作是免费的,但这种遗漏的优化在内部函数中很普遍,也会影响标量内部函数,如\u mm\u popcnt\u u32
还有一个问题是gcc(不知道)实际结果只在其32位int
结果的低位16设置了位(除非您使用了AVX2vpmovmskb ymm
)。因此,实际符号扩展是不必要的;隐式零扩展完全可以
有没有办法可靠地避免在流行的编译器上生成不必要的指令?特别是,当结果用作索引时,即在x=array[_mm_movemask_epi8(xmmValue)]中代码>
不,除了修复gcc。是否有人将此报告为编译器遗漏优化错误
叮当没有这个错误。我在Paul R的测试中添加了代码,以实际使用结果作为数组索引,而clang仍然可以
(在这种情况下,可能是因为它想“保留”RAX底部的32位值,而不是因为它在优化mov消除
强制转换为未签名有助于GCC6和更高版本的使用;它将直接使用pmovskb
结果作为寻址模式的一部分,但也会以mov-rax,rdx
返回结果
对于较旧的GCC,至少可以让它使用mov
而不是movsxd
或cdqe
在现代CPU架构上,不必要的指令(如mov eax,eax
)的大约成本是多少?这些指令是否有可能被CPU内部完全消除,并且它们实际上不占用执行单元的时间(Agner Fog的指令表文档提到了这种可能性)
mov-same,same
在SnB系列微体系结构或AMD-zen上从未被消除。mov-ecx,eax
将被消除。有关详细信息,请参阅
即使不需要执行单元,它仍然需要管道的融合域部分中的一个插槽,以及uop缓存中的一个插槽。和代码大小。如果您接近前端每时钟限制4个融合域uop(管道宽度),那么这就是一个问题
它还需要在dep链中额外花费1c的延迟
(不过后端吞吐量不是问题。在Haswell和更新版本上,它可以在没有向量执行单元的port6上运行。在AMD上,整数端口与向量端口是分开的。)Reg