来自SSE2的内在函数_mm_movemask_epi8
由英特尔定义,具有以下原型:
int _mm_movemask_epi8 (__m128i a);
此内部函数直接对应于pmovmskb
指令,该指令由所有编译器生成。
根据this reference,pmovmskb
指令可以将结果整数掩码写入x64模式下的32位或64位通用寄存器。在任何情况下,结果只有16个低位可以是非零的,即结果肯定在[0; 65535]。
说到内部函数_mm_movemask_epi8
,它的返回值是int
类型,在大多数平台上都是32位大小的有符号整数。不幸的是,没有替代函数在x64模式下返回64位整数。结果:
pmovmskb
)生成eax
指令。rax
)为零。mov eax, eax
)插入到零位64位寄存器的上半部分,假设该寄存器稍后用作64位值(例如作为数组的索引)。在this answer中可以看到具有此类问题的代码示例和生成的程序集。对该答案的评论也包含一些相关的讨论。我经常使用MSVC2013编译器遇到这个问题,但它似乎也出现在GCC上。
问题是:
x = array[_mm_movemask_epi8(xmmValue)];
mov eax, eax
等不必要指令的大概成本是多少?这些指令是否有可能被CPU内部完全消除,并且它们实际上并没有占用执行单元的时间(Agner Fog的指令表文档提到了这种可能性)。答案 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>
在SnB家族微体系结构中,现代CPU架构上不必要的指令(如
mov eax, eax
)的大致成本是多少?这些指令是否有可能被CPU内部完全消除,并且实际上并没有占用执行单元的时间(Agner Fog的指令表文档提到了这种可能性)。
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