SSE内置函数检查零标志位

3

我想知道是否可以通过英特尔的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);

这里,编译器会生成pcmpistrmpcmpistri指令。然而,在我看来,第二条指令是冗余的,因为pcmpistrm以与pcmistri相同的方式设置处理器标志寄存器中的标志。
那么,回到我的问题,有没有一种方法可以直接读取标志寄存器或指示编译器只生成pcmpistrm指令?

1
哪个编译器以及使用什么选项?这似乎更像是编译器成功进行CSE的问题。ISA手册列出了_mm_cmpistrz作为PCMPISTRIPCMPISTRM的内部函数之一,因此根据英特尔的说法,编译器可以为_mm_cmpistrz发出任一指令。 - Peter Cordes
另外,您能否将此内容封装在一个可编译的函数中,以便人们可以将其复制到http://gcc.godbolt.org/上?或者更好的是,您自己在Godbolt上链接源代码和汇编输出。 - Peter Cordes
@Peter Cordes 我使用启用了所有优化选项(/O2)的MSVC编译器。 - Philipp Neufeld
看起来只是 MSVC 的优化问题。gcc6.2 和 icc17 成功使用了一个 PCMPISTRM 的两个结果(https://godbolt.org/g/4wRR8o),在我编写的一个测试函数中,该函数会根据“zero”结果进行分支。另一方面,clang3.9 失败了,并使用了一个 PCMPISTRI。 - Peter Cordes
2个回答

2
看起来只是 MSVC 的优化错误,没有任何固有的问题。
“gcc6.2和icc17成功地在一个测试函数中使用了来自PCMPISTRM的两个结果,该函数根据零结果进行分支(请参见Godbolt编译器资源管理器):”
#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

另一方面,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的指令表,PCMPISTRM具有良好的吞吐量,但延迟较高,因此如果延迟是瓶颈,则可以同时执行两个操作。像使用__readflags()这样的花招实际上可能更糟糕。

1
我自己找到了解决方案。
有一个名为__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)

这个解决方案不是最优的,但它能起到作用。

1
我真的很担心优化会将PCMPISTRM与PUSHF分开,并导致从整数加/减法或其他地方读取标志。如果这是可靠的,那么将标志写入堆栈然后进行TEST的5个周期存储转发延迟可能比大多数CPU上的另一个PCMPISTRI更好,至少对于吞吐量而言。对于延迟来说,它可能更糟,因为PCMPISTRM具有良好的吞吐量但延迟高,因此并行运行两个以产生相同结果可能比额外的5c更好! - Peter Cordes
你说得对!我刚刚对这两种解决方案进行了基准测试,使用 pushf 的那个实际上比同时使用 pcmpistrmpcmpistri 的那个慢了约1纳秒。 - Philipp Neufeld
请注意您的基准测试反映了您的实际用例。延迟与吞吐量是一个大问题。 - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接