【发布时间】:2016-07-01 05:34:16
【问题描述】:
来自 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 位整数。结果:
- 编译器通常会生成带有 32 位目标寄存器的
pmovmskb指令(例如eax)。 - 编译器不能假定整个寄存器的高 32 位(例如
rax)为零。 - 编译器插入不必要的指令(例如
mov eax, eax)将 64 位寄存器的上半部分归零,因为该寄存器稍后用作 64 位值(例如,作为数组的索引)。
可以在this answer 中查看具有此类问题的代码示例和生成的程序集。该答案的 cmets 也包含一些相关的讨论。我经常在使用 MSVC2013 编译器时遇到这个问题,但似乎它也存在于 GCC 上。
问题是:
- 为什么会这样?
- 有什么方法可以可靠地避免在流行的编译器上生成不必要的指令?特别是当结果用作索引时,即在
x = array[_mm_movemask_epi8(xmmValue)]; - 在现代 CPU 架构上,像
mov eax, eax这样的不必要指令的大致成本是多少?这些指令是否有可能在内部被 CPU 完全消除,并且它们实际上并不占用执行单元的时间(Agner Fog 的指令表文档提到了这种可能性)。
【问题讨论】:
-
关于问题 #2,您可以随时下拉到内联汇编,尽管我的记忆告诉我,也许 MSVC 不支持。
-
@JasonR:是的,MSVC x64 没有内联汇编。此外,在 MSVC x32 内联汇编中完全禁止优化。但是在一般情况下重要的是,使用编译器而不是汇编可以从内联函数中编写高效的代码。它甚至允许使用 C++ 模板制作高度可配置和可读的代码。更不用说用内部函数编写对于程序员来说更容易、更快,而且更便携。
-
我同意;内在函数当然是可取的。关于问题 #3,您是否尝试过在您的应用程序中对其进行分析?除非这是在一个内部循环中,否则我怀疑额外的指令会产生有意义的影响,特别是如果你在那里也有内存访问。
-
cdqe - 将 32 位扩展到 64 位,以 1 个时钟延迟执行。如果你没有触及寄存器的上半部分,我已经看到 gcc clear 一次(比如在循环之外)并跳过这个。
标签: gcc 64-bit x86-64 sse micro-optimization