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 的解决方案实际上比同时使用pcmpistrmpcmpistri 的解决方案慢1ns。 小心你的基准反映了你的真实用例。延迟与吞吐量很重要。

以上是关于SSE 内在函数检查零标志的主要内容,如果未能解决你的问题,请参考以下文章

如何检查 256i(16 位)向量以了解它是不是包含任何大于零的元素?

SSE 内在函数优化

数组乘法与 sse 内在函数乘法的时序?

用 sse 执行内在函数

内在函数和寄存器(SSE)

SSE 内在函数向右移位