如何使用 SIMD 检查偶数/奇数车道是否在给定范围内?

问题描述 投票:0回答:1

给定一个存储 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)
x86 simd sse
1个回答
0
投票

制作一个每车道

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
不需要屏蔽掉我们不关心的元素。)

© www.soinside.com 2019 - 2024. All rights reserved.