SSE 内在函数检查零标志
Posted
技术标签:
【中文标题】SSE 内在函数检查零标志【英文标题】:SSE intrinsics check zero flag 【发布时间】:2016-11-12 14:01:59 【问题描述】:我想知道是否可以通过 Intel 的 SSE 内在函数检查处理器的标志寄存器?
例如:
int idx = _mm_cmpistri(mmrange, mmstr, 0x14);
int zero = _mm_cmpistrz(mmrange, mmstr, 0x14);
在此示例中,编译器能够将这两个内在函数优化为一条指令 (pcmpistri
) 并通过跳转指令 (jz
) 检查标志寄存器。
但是在以下示例中,编译器无法正确优化代码:
__m128i mmmask = _mm_cmpistrm(mmoldchar, mmstr, 0x40);
int zero = _mm_cmpistrz(mmoldchar, mmstr, 0x40);
在这里,编译器生成一个pcmpistrm
和一个pcmpistri
指令。但是,在我看来,第二条指令是多余的,因为pcmpistrm
在处理器的标志寄存器中设置标志的方式与pcmistri
相同。
那么,回到我的问题,有没有办法直接读取标志寄存器或指示编译器只生成pcmpistrm
指令?
【问题讨论】:
哪个编译器有什么选项?这似乎更多是编译器成功进行 CSEing 的问题。 ISA 手册将_mm_cmpistrz
列为PCMPISTRI 和PCMPISTRM 的内在函数之一,因此根据英特尔的说法,编译器可以为_mm_cmpistrz
发出任一指令。
另外,你能把它包装在一个可以编译的函数中,这样人们就可以把它复制到gcc.godbolt.org吗?或者更好的是,自己链接到 Godbolt 上的源 + asm 输出。
@Peter Cordes 我使用启用了所有优化的 MSVC 编译器 (/O2)
看起来只是一个 MSVC 错过优化错误。 gcc6.2 和 icc17 在我编写的测试函数中成功使用了来自一个 PCMPISTRM (godbolt.org/g/4wRR8o) 的两个结果,该函数在 zero
结果上出现了分支。 OTOH,clang3.9 失败,使用 PCMPISTRI。
【参考方案1】:
看起来只是一个 MSVC 错过优化的错误,而不是任何固有的错误。
gcc6.2 和 icc17 在我编写的测试函数中成功使用了来自一个 PCMPISTRM 的两个结果,该函数在 zero
结果 (on the Godbolt compiler explorer) 上发生了分支:
#include <immintrin.h>
__m128i foo(__m128i mmoldchar, __m128i mmstr)
__m128i mmmask = _mm_cmpistrm(mmoldchar, mmstr, 0x40);
int zero = _mm_cmpistrz(mmoldchar, mmstr, 0x40);
if(zero)
return mmmask;
else
return _mm_setzero_si128();
##gcc6.2 -O3 -march=nehalem
pcmpistrm xmm0, xmm1, 64
je .L5
pxor xmm0, xmm0
ret
.L5:
ret
OTOH,clang3.9 无法通过 CSE,并使用 PCMPISTRI。
foo:
movdqa xmm2, xmm0
pcmpistri xmm2, xmm1, 64
pxor xmm0, xmm0
jne .LBB0_2
pcmpistrm xmm2, xmm1, 64
.LBB0_2:
ret
请注意,根据Agner Fog's instruction tables,PCMPISTRM 具有良好的吞吐量,但延迟很高,因此如果延迟是瓶颈,那么并行执行两个操作的空间很大。像使用__readflags()
这样的跳跃实际上可能更糟。
【讨论】:
【参考方案2】:我自己找到了解决方案。
有一个函数可以读取标志寄存器,称为__readeflags()
。它包装了pushf
(x64 平台上的pushfq
)指令。
现在的代码如下所示:
__m128i mmmask = _mm_cmpistrm(mmoldchar, mmstr, 0x40);
int zero = __readeflags() & 0x40; //0x40 is the mask for the zero flag (bit 6)
这个解决方案不是最优的,但它可以解决问题。
【讨论】:
我非常担心优化可能会将 PCMPISTRM 与 PUSHF 分开,并导致从整数加/减或其他内容中读取标志。如果这是可靠的,那么在大多数 CPU 上,将标志写入堆栈然后测试它们的大约 5 个周期的存储转发延迟可能比另一个 PCMPISTRI 更好,至少在吞吐量方面是这样。对于延迟,可能会更糟,因为 PCMPISTRM 具有良好的吞吐量但延迟较高,因此并行运行两个以产生相同的结果两次可能比额外的 5c 更好! 你是对的!我刚刚对这两种解决方案进行了基准测试,使用pushf
的解决方案实际上比同时使用pcmpistrm
和pcmpistri
的解决方案慢1ns。
小心你的基准反映了你的真实用例。延迟与吞吐量很重要。以上是关于SSE 内在函数检查零标志的主要内容,如果未能解决你的问题,请参考以下文章