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

时间:2018-07-08 06:36:07

标签: c++ arm intrinsics neon

我在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;
}

基本上,我想清除除其中一个设定的车道之外的所有车道。明显的straightforward implementation in 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;
}

如何在臂霓虹灯中更有效地做到这一点?

2 个答案:

答案 0 :(得分:2)

Very simple

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也可以代替零进行比较。

答案 1 :(得分:1)

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

但是,如果您在循环中使用它并具有3个备用矢量寄存器,则只能用一个元素进行XOR运算,而不能用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;
}

Godbolt

/* 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 进行AND运算。)在其他元素中全为1的veor替换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