PCMPGTQ 是在 sse4.2 中引入的,它为产生掩码的 64 位数字提供大于符号的比较。
在 sse4.2 之前的指令集上如何支持此功能?
更新:同样的问题适用于带有 Neon 的 ARMv7,它也缺少 64 位比较器。在这里可以找到与此相关的姊妹问题: 在 ARMv7a 与 Neon 上支持 CMGT 与 64 位签名比较的最有效方法是什么?
__m128i pcmpgtq_sse2 (__m128i a, __m128i b) {
__m128i r = _mm_and_si128(_mm_cmpeq_epi32(a, b), _mm_sub_epi64(b, a));
r = _mm_or_si128(r, _mm_cmpgt_epi32(a, b));
return _mm_shuffle_epi32(r, _MM_SHUFFLE(3,3,1,1));
}
我们有 32 位有符号比较内在函数,因此将打包的 qwords 拆分为 dwords 对。
如果 in 的高 dworda
大于 in 的高 dword,b
则无需比较低 dword。
if (a.hi > b.hi) { r.hi = 0xFFFFFFFF; }
if (a.hi <= b.hi) { r.hi = 0x00000000; }
如果输入的高 dworda
等于输入的高 dword,b
则 64 位减法将清除或设置结果的所有 32 高位(如果高 dword 相等,则它们相互“抵消”,实际上是无符号比较低双字,将结果放在高双字中)。
if (a.hi == b.hi) { r = (b - a) & 0xFFFFFFFF00000000; }
将高 32 位的比较掩码复制到低 32 位。
r.lo = r.hi
更新:这是 SSE2 和 ARMv7+Neon 的Godbolt。
下面的代码演示了可以使用预先存在的 SSE 指令通过从第一原理工作来模拟所需的功能:有符号的“小于”谓词为真等效于溢出标志与减法后的标志标志不同。显然我们没有标志,因此也需要模拟标志,将结果谓词留在每个四字的最高有效位 (MSB) 中。在第二步中,我们可以将 MSB 扩展为四字大小的掩码。
由此产生的相当长的代码很好地表明这可能不是从性能角度模拟所需功能的最佳方式,即使它在功能上是正确的。编译器资源管理器 (godbolt.org) 在使用 Clang 11 进行编译时显示了 12 条指令(请参阅下面的程序集清单)。
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include "nmmintrin.h"
#define USE_SSE42_REF (0)
#define NBR_OF_TESTS (1000000000)
#if USE_SSE42_REF
__m128i pcmpgtq_ref (__m128i a, __m128i b)
{
return _mm_cmpgt_epi64 (a, b);
}
#else // USE_SSE42_REF
__m128i pcmpgtq_ref (__m128i a, __m128i b)
{
__m128i r;
struct {
int64_t lo;
int64_t hi;
} hilo_a, hilo_b, hilo_r;
memcpy (&hilo_a, &a, sizeof hilo_a);
memcpy (&hilo_b, &b, sizeof hilo_b);
hilo_r.lo = hilo_a.lo > hilo_b.lo ? (-1LL) : 0LL;
hilo_r.hi = hilo_a.hi > hilo_b.hi ? (-1LL) : 0LL;
memcpy (&r, &hilo_r, sizeof r);
return r;
}
#endif // USE_SSE42_REF
/* "signed less than" == (OF != SF); compute predicate in MSB of each byte */
__m128i ltq_core (__m128i a, __m128i b)
{
__m128i m = _mm_set1_epi64x (0x7fffffffffffffffULL);
__m128i c = _mm_and_si128 (b, m);
__m128i d = _mm_andnot_si128 (a, m);
__m128i t = _mm_add_epi64 (c, d);
__m128i s = _mm_xor_si128 (a, b);
__m128i x = _mm_xor_si128 (a, t);
__m128i y = _mm_and_si128 (x, s);
__m128i r = _mm_xor_si128 (y, t);
return r;
}
/* extend sign bits into mask, quadword-wise */
__m128i q_sign_to_mask (__m128i a)
{
__m128i q = _mm_set1_epi64x (0);
__m128i s = _mm_srli_epi64 (a, 63);
__m128i r = _mm_sub_epi64 (q, s);
return r;
}
__m128i pcmpltq (__m128i a, __m128i b)
{
return q_sign_to_mask (ltq_core (a, b));
}
__m128i pcmpgtq (__m128i a, __m128i b)
{
return pcmpltq (b, a);
}
/*
https://groups.google.com/forum/#!original/comp.lang.c/qFv18ql_WlU/IK8KGZZFJx4J
From: geo <gmars...@gmail.com>
Newsgroups: sci.math,comp.lang.c,comp.lang.fortran
Subject: 64-bit KISS RNGs
Date: Sat, 28 Feb 2009 04:30:48 -0800 (PST)
This 64-bit KISS RNG has three components, each nearly
good enough to serve alone. The components are:
Multiply-With-Carry (MWC), period (2^121+2^63-1)
Xorshift (XSH), period 2^64-1
Congruential (CNG), period 2^64
*/
static uint64_t kiss64_x = 1234567890987654321ULL;
static uint64_t kiss64_c = 123456123456123456ULL;
static uint64_t kiss64_y = 362436362436362436ULL;
static uint64_t kiss64_z = 1066149217761810ULL;
static uint64_t kiss64_t;
#define MWC64 (kiss64_t = (kiss64_x << 58) + kiss64_c, \
kiss64_c = (kiss64_x >> 6), kiss64_x += kiss64_t, \
kiss64_c += (kiss64_x < kiss64_t), kiss64_x)
#define XSH64 (kiss64_y ^= (kiss64_y << 13), kiss64_y ^= (kiss64_y >> 17), \
kiss64_y ^= (kiss64_y << 43))
#define CNG64 (kiss64_z = 6906969069ULL * kiss64_z + 1234567ULL)
#define KISS64 (MWC64 + XSH64 + CNG64)
int main (void)
{
struct {
uint64_t lo;
uint64_t hi;
} hilo_a, hilo_b, hilo_res, hilo_ref;
for (int i = 0; i < NBR_OF_TESTS; i++) {
uint64_t al = KISS64;
uint64_t ah = KISS64;
uint64_t bl = KISS64;
uint64_t bh = KISS64;
if ((i & 0xff) == 0x00) bl = al; // increase chance of equality
if ((i & 0xff) == 0xff) bh = ah; // increase chance of equality
__m128i a = _mm_set_epi64x (ah, al);
__m128i b = _mm_set_epi64x (bh, bl);
__m128i res = pcmpgtq (a, b);
__m128i ref = pcmpgtq_ref (a, b);
memcpy (&hilo_res, &res, sizeof hilo_res);
memcpy (&hilo_ref, &ref, sizeof hilo_ref);
if ((hilo_res.hi != hilo_ref.hi) || (hilo_res.lo != hilo_ref.lo)) {
memcpy (&hilo_a, &a, sizeof hilo_a);
memcpy (&hilo_b, &b, sizeof hilo_b);
printf ("error: a=%016llx_%016llx b=%016llx_%016llx res=%016llx_%016llx ref=%016llx_%016llx\n",
hilo_a.hi, hilo_a.lo, hilo_b.hi, hilo_b.lo,
hilo_res.hi, hilo_res.lo, hilo_ref.hi, hilo_ref.lo);
return EXIT_FAILURE;
}
}
return EXIT_SUCCESS;
}
使用 编译时-msse2
,Clang 11 的输出如下所示:
.LCPI4_0:
.quad 9223372036854775807 # 0x7fffffffffffffff
.quad 9223372036854775807 # 0x7fffffffffffffff
pcmpgtq: # @pcmpgtq
movdqa xmm2, xmmword ptr [rip + .LCPI4_0] # xmm2 = [9223372036854775807,9223372036854775807]
movdqa xmm3, xmm1
pxor xmm3, xmm0
pand xmm0, xmm2
movdqa xmm4, xmm1
pandn xmm4, xmm2
paddq xmm4, xmm0
pand xmm1, xmm3
pandn xmm3, xmm4
por xmm3, xmm1
psrad xmm3, 31
pshufd xmm0, xmm3, 245 # xmm0 = xmm3[1,1,3,3]
ret
我不确定这是否是最佳输出,但这是 Clang 的 x64 输出。我也采用了相同的实现并将其转换为支持带有霓虹灯的 armv7。
请参阅下面的Godbolt和组件:
.LCPI0_0:
.quad 2147483648 # 0x80000000
.quad 2147483648 # 0x80000000
cmpgtq_sim(long __vector(2), long __vector(2)): # @cmpgtq_sim(long __vector(2), long __vector(2))
movdqa xmm2, xmmword ptr [rip + .LCPI0_0] # xmm2 = [2147483648,2147483648]
pxor xmm1, xmm2
pxor xmm0, xmm2
movdqa xmm2, xmm0
pcmpgtd xmm2, xmm1
pshufd xmm3, xmm2, 160 # xmm3 = xmm2[0,0,2,2]
pcmpeqd xmm0, xmm1
pshufd xmm1, xmm0, 245 # xmm1 = xmm0[1,1,3,3]
pand xmm1, xmm3
pshufd xmm0, xmm2, 245 # xmm0 = xmm2[1,1,3,3]
por xmm0, xmm1
ARMv7+霓虹灯
cmpgtq_sim(__simd128_int64_t, __simd128_int64_t):
vldr d16, .L3
vldr d17, .L3+8
veor q0, q0, q8
veor q1, q1, q8
vcgt.s32 q8, q0, q1
vceq.i32 q1, q0, q1
vmov q9, q8 @ v4si
vmov q10, q1 @ v4si
vtrn.32 q9, q8
vmov q0, q8 @ v4si
vmov q8, q1 @ v4si
vtrn.32 q10, q8
vand q8, q8, q9
vorr q0, q8, q0
bx lr
.L3:
.word -2147483648
.word 0
.word -2147483648
.word 0