SSE 内在函数检查零标志
SSE intrinsics check zero flag
我想知道是否可以通过 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
指令?
我自己找到了解决办法。
有一个名为__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)
这个解决方案不是最优的,但可以解决问题。
看起来只是一个 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()
这样的跳跃实际上可能更糟。
我想知道是否可以通过 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
指令?
我自己找到了解决办法。
有一个名为__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)
这个解决方案不是最优的,但可以解决问题。
看起来只是一个 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()
这样的跳跃实际上可能更糟。