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

时间:2016-03-15 16:34:42

标签: 64-bit sse micro-optimization

来自SSE2的内在函数_mm_movemask_epi8由英特尔定义,具有以下原型:

  int _mm_movemask_epi8 (__m128i a);

此内部函数直接对应于pmovmskb指令,该指令由所有编译器生成。

根据this referencepmovmskb指令可以将结果整数掩码写入x64模式下的32位或64位通用寄存器。在任何情况下,结果只有16个低位可以是非零的,即结果肯定在[0; 65535]。

说到内部函数_mm_movemask_epi8,它的返回值是int类型,在大多数平台上都是32位大小的有符号整数。不幸的是,没有替代函数在x64模式下返回64位整数。结果:

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

    问题是:

    1. 为什么会这样?
    2. 有没有办法可靠地避免在热门编译器上生成不必要的指令?特别是,当结果用作索引时,即在x = array[_mm_movemask_epi8(xmmValue)];
    3. 现代CPU架构上mov eax, eax等不必要指令的大概成本是多少?这些指令是否有可能被CPU内部完全消除,并且它们实际上并没有占用执行单元的时间(Agner Fog的指令表文档提到了这种可能性)。

2 个答案:

答案 0 :(得分:4)

  

为什么会这样?

gcc的内部数据告诉我pmovmskb必须告知它eax的高32位总是为零。它可能被视为函数调用,其中ABI允许返回32位int的函数在rax的高32位中留下垃圾。

  

有没有办法可靠地避免在热门编译器上生成不必要的指令?特别是,当结果用作索引时,即在x = array[_mm_movemask_epi8(xmmValue)];

不,除了修复gcc。有没有人将此报告为编译器遗漏优化错误?

clang没有这个bug。我在Paul R的测试中添加了代码,实际上将结果用作数组索引,而clang仍然很好。

gcc always either zero or sign extends(在这种情况下是一个不同的寄存器,但我认为只是因为它想要返回eax,而不是因为它正在优化mov-elimination。转换为unsigned没有用。< / p>

  

现代CPU架构上不必要的指令(如mov eax, eax)的大致成本是多少?这些指令是否有可能被CPU内部完全消除,并且实际上并没有占用执行单元的时间(Agner Fog的指令表文档提到了这种可能性)。

在SnB家族微体系结构中,

mov same,same从未被消除。 mov ecx, eax会被淘汰(大多数情况下?我从来没有发现任何具体关于何时reg,reg移动可能不会被淘汰的内容)。 AMD消除了矢量reg,reg在重命名阶段移动,如Intel,但不是整数寄存器移动。即使它不占用执行单元,它仍然需要在管道的融合域部分中使用一个槽,并在uop-cache中使用一个槽。和代码大小。如果你接近每个时钟管道宽度的4个融合域uops,那么这是一个问题。

如果没有消除,最大的问题是额外的1c延迟。对于吞吐量,它可能非常小。在Haswell和更新版本上,它可以在port6上运行(没有向量执行单元)。在AMD上,整数端口与向量端口分开。

答案 1 :(得分:2)

gcc.godbolt.org是一个很好的在线资源,用于测试不同编译器的此类问题。

clang似乎在这方面做得最好,例如

#include <xmmintrin.h>
#include <cstdint>

int32_t test32(const __m128i v) {
  int32_t mask = _mm_movemask_epi8(v);
  return mask;
}

int64_t test64(const __m128i v) {
  int64_t mask = _mm_movemask_epi8(v);
  return mask;
}

产生

test32(long long __vector(2)):                         # @test32(long long __vector(2))
        vpmovmskb       eax, xmm0
        ret

test64(long long __vector(2)):                         # @test64(long long __vector(2))
        vpmovmskb       eax, xmm0
        ret

gcc在64位情况下生成额外的cdqe指令:

test32(long long __vector(2)):
        vpmovmskb       eax, xmm0
        ret
test64(long long __vector(2)):
        vpmovmskb       eax, xmm0
        cdqe
        ret