给定一个存储 16 个
__m128i
s 的 char
,偶数索引车道指的是 even 车道(即位于 0、2、4、...、14 的车道),奇数索引车道指的是奇数 车道(即 1、3、5、... 15 处的车道)。
在我的应用程序中,偶数/奇数车道必须在给定范围内。例如,假设
even_min
为1,even_max
为7,odd_min
为5,odd_max
为10:
# valid
vec1: [1, 5, 6, 10, 2, 6, 4, 6, 2, 7, 4, 9, 2, 7, 4, 8]
# invalid because 0-th (even) is greater than even_max
vec2: [8, 5, 6, 10, 2, 6, 4, 6, 2, 7, 4, 9, 2, 7, 4, 8]
如何更高效地检查是否有效?
我目前的解决方案很简单,分别查看两个比较结果:
__m128i even_min = _mm_set1_epi8(xxx);
__m128i even_max = _mm_set1_epi8(xxx);
__m128i even_mask =
_mm_set_epi8(0, -1, 0, -1, 0, -1, 0, -1, 0, -1, 0, -1, 0, -1, 0, -1);
__m128i evenRange = _mm_and_si128(_mm_cmpge_epi8(vec, even_min),
_mm_cmple_epi8(vec, even_max));
bool isEvenOk = _mm_testc_si128(evenRange, even_mask);
// the code for checking odd bytes is similar
注意,要使用 inclusive 条件比较无符号字符,两个宏定义如下:
#define _mm_cmpge_epi8(a, b) _mm_cmpeq_epi8(_mm_max_epu8(a, b), a)
#define _mm_cmple_epi8(a, b) _mm_cmpge_epi8(b, a)
制作一个每车道
min
值的向量,另一个每车道max
es的向量。例如,_mm_set1_epi16((odd_min<<8) | (uint8_t)even_min)
。 (注意转换以避免符号扩展)。
然后你只需要一次范围检查。你应该更有效地做,而不是通过每个 2 条指令模拟 cmpge 和 cmple。正如安德烈在评论中所建议的,一个简单的方法是
v == min(max(v, a), b)
,这与您的v == min(v, a)
相同。
其他可能性包括
(v < mins) | (v > maxes)
和检查没有元素是真的。 _mm_movemask_epi8(or_result) == 0
。这具有更好的关键路径延迟,因为我们有两个独立的比较,而不是 3 个操作链。两种方式都需要原始矢量的副本(除非您使用 AVX 进行编译以允许单独的目标)。
或
(v > min-1) & (v < max+1)
,这对于编译时常数最小值/最大值是可行的。如果 min 已经是 INT8_MIN,则它始终为真,因此它优化为只需要另一个条件。除了当 even_min 是 -128 但 odd_min 是其他东西时这是一个问题:没有任何值可以使 pcmpgtb
对于偶数通道中的所有输入始终为真,同时仍然检查奇数通道。我一直认为AND可以作为ptest
(_mm_test_*
)的一部分来完成,但实际上没有_mm_test_all_ones
。如果 128 位与结果中有任何非零位,则 ZF 被清除。 (CF 也一样,基于 ANDN 结果。)
或者两次使用
cmpgt
并反转其中一个结果作为组合它们的一部分,例如与_mm_andnot_si128
(pandn
)
ptest
在比较结果上不是很有效,因为它在大多数 CPU 上解码为 2 微指令; pmovmskb
+ 标量 cmp
或 test
也是 2 uops (https://uops.info),并且 cmp 或测试可以与分支宏融合,如果你在它上面分支。 ptest
确实避免了需要一个临时寄存器,并且如果您正在测试一个您也想稍后使用的向量(不是比较结果),则可能会保存一个 movdqa
寄存器副本,但通常只有在您实际使用时才好它只检查某些元素的能力(比如你的奇数/偶数掩码)。
在您的情况下,即使采用两次单独比较的策略,更好的策略可能是 2x
_mm_movemask_epi8
和 (evens & (odds>>1) & 0x5555 == 0x5555
。 (0x5555 是 0b0101...0101,只是测试偶数元素)。
或
_mm_srli_epi16(odds, 8)
/ _mm_and_si128(evens, shifted_odds)
以获得一个向量,其中偶数元素具有您关心的结果。 (奇数元素为零,因为逻辑移位在那里产生了零,所以_mm_movemask_epi8(and_result) == 0x5555
不需要屏蔽掉我们不关心的元素。)