Gcc 在x64模式下为_mm_movemask_epi8内部生成不必要的指令

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,在大多

来自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
,在大多数平台上它是32位大小的有符号整数。不幸的是,在x64模式下没有其他函数返回64位整数。因此:

  • 编译器通常使用32位目标寄存器生成
    pmovmskb
    指令(例如
    eax
  • 编译器不能假定整个寄存器的上32位(例如,
    rax
    )为零
  • 编译器插入不必要的指令(例如,
    mov eax,eax
    )将64位寄存器的上半部分归零,因为该寄存器后来用作64位值(例如,作为数组的索引)
  • 在中可以看到带有此类问题的代码和生成的程序集的示例。对该答案的评论也包含一些相关的讨论。我经常在MSVC2013编译器中遇到这个问题,但它似乎也存在于GCC中

    问题是:

  • 为什么会这样
  • 有没有办法可靠地避免在流行的编译器上生成不必要的指令?特别是,当结果用作索引时,即在
    x=array[_mm_movemask_epi8(xmmValue)]中
  • 在现代CPU架构上,不必要的指令(如
    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设置了位(除非您使用了AVX2
    vpmovmskb 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