如何清除霓虹灯中除第一个非零车道之外的所有车道?

How to clear all but the first non-zero lane in neon?

本文关键字:零车 清除 何清除 霓虹灯 第一个      更新时间:2023-10-16

我在uint32x4_t霓虹灯寄存器中有一个掩码。在这个掩码中,至少设置了 4 个整数中的 1 个(例如 0xffffffff(,但是,我可能有寄存器中设置了多个项目的情况。如何确保只设置一个?

在 C 伪代码中:

uint32x4_t clearmask(uint32x4_t m)
{
if (m[0]) { m[1] = m[2] = m[3] = 0; }
else if (m[1]) { m[2] = m[3] = 0; }
else if (m[2]) { m[3] = 0; }
return m;
}

基本上,我想清除除一条设置车道之外的所有车道。neon 中显而易见的直接实现可能是:

uint32x4_t cleanmask(uint32x4_t m)
{
uint32x4_t mx;
mx = vdupq_lane_u32(vget_low_u32(vmvnq_u32(m)), 0);
mx = vsetq_lane_u32(0xffffffff, mx, 0);
m = vandq_u32(m, mx);
mx = vdupq_lane_u32(vget_low_u32(vmvnq_u32(m)), 1);
mx = vsetq_lane_u32(0xffffffff, mx, 1);
m = vandq_u32(m, mx);
mx = vdupq_lane_u32(vget_high_u32(vmvnq_u32(m)), 0);
mx = vsetq_lane_u32(0xffffffff, mx, 2);
m = vandq_u32(m, mx);
return m;
}

如何在手臂霓虹灯中更有效地完成此操作?

很简单:

vceq.u32    q1, q0, #0
vmov.i8     d7, #0xff
vext.8      q2, q3, q1, #12
vand        q0, q0, q2
vand        d1, d1, d2
vand        d1, d1, d4

总共 6 条指令,如果可以保持 Q3 恒定,则为 5 条。

下面的aarch64版本必须更容易理解:

cmeq    v1.4s, v0.4s, #0
movi    v31.16b, #0xff
ext     v2.16b, v31.16b, v1.16b, #12
ext     v3.16b, v31.16b, v1.16b, #8
ext     v4.16b, v31.16b, v1.16b, #4
and     v0.16b, v0.16b, v2.16b
and     v0.16b, v0.16b, v3.16b
and     v0.16b, v0.16b, v4.16b

这是如何工作的

ext/vext从两个向量的串联中获取一个窗口,因此我们正在创建掩码

v0 = [  d   c   b   a ]
v2 = [ !c  !b  !a  -1 ]
v3 = [ !b  !a  -1  -1 ]
v4 = [ !a  -1  -1  -1 ]

如果前面的任何元素不为零,则最高元素(d(为零。

第二高的元素(c(如果前面的任何元素(ab(不为零,则为零。 等等。


当元素保证为 0 或 -1 时,mvn也可以代替与零进行比较。

我的想法与您的未注释代码几乎相同:如果设置了该元素,则将倒置元素作为 AND 掩码广播到零个后续元素,否则保持向量不变。

但是,如果您在循环中使用它并且有 3 个备用向量寄存器,则您不能使用异或除一个元素之外的所有元素,而不是 MVN + 设置一个元素。

vdupq_lane_u32(vget_low_u32(m), 1);似乎可以有效地编译为vdup.32 q9, d16[1],并且我的代码的那部分与您的代码相同(但没有 MVN(。

不幸的是,这是一个很长的串行依赖链;我们正在从 AND 结果创建下一个掩码,所以没有 ILP。 我没有看到一种好方法可以在降低延迟的同时仍然获得所需的结果。

uint32x4_t cleanmask_xor(uint32x4_t m)
{
//                 {  a    b    c   d }
uint32x4_t maska = {  0, ~0U, ~0U, ~0U};
uint32x4_t maskb = {~0U,   0, ~0U, ~0U};
uint32x4_t maskc = {~0U, ~0U,   0, ~0U};
uint32x4_t tmp = vdupq_lane_u32(vget_low_u32(m), 0);
uint32x4_t aflip = tmp ^ maska;
m &= aflip;  // if a was non-zero, the rest are zero
tmp = vdupq_lane_u32(vget_low_u32(m), 1);
uint32x4_t bflip = tmp ^ maskb;
m &= bflip;  // if b was non-zero, the rest are zero
tmp = vdupq_lane_u32(vget_high_u32(m), 0);
uint32x4_t cflip = tmp ^ maskc;
m &= cflip;  // if b was non-zero, the rest are zero
return m;
}

(戈博尔特(

/* design notes
[ a   b   c   d ]
[ a  ~a  ~a  ~a ] 
&:[ a   0   0   0 ]
or[ 0   b   c   d ]
= [ e   f   g   h  ]
[ ~f  f   ~f  ~f ]  // not b, because f can be zero when b isn't
= [ i   j   k   l ]
...
*/

当负载被吊出一个循环时,这只是 9 条指令而不是 12 条指令,因为我们跳过了vmov.32 d1[0], r3或任何在每个掩码中插入-1的东西。 (与元素自身一起与 AND 等效于与-1U一起做。veor其他元素中的全 1 替换了vmvn

Clang在加载多个向量常量方面似乎效率低下:它分别设置每个地址,而不仅仅是将它们存储在可以从一个基本指针到达的位置。 因此,您可能需要考虑创建 3 个常量的替代策略。

#if 1
// clang sets up the address of each constant separately
//                 {  a    b    c   d }
uint32x4_t maska = {  0, ~0U, ~0U, ~0U};
uint32x4_t maskb = {~0U,   0, ~0U, ~0U};
uint32x4_t maskc = {~0U, ~0U,   0, ~0U};
#else
static const uint32_t maskbuf[] = 
{ -1U, -1U, 0, -1U, -1U, -1U};
// unaligned loads.
// or load one + shuffle?
#endif