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

Posted

技术标签:

【中文标题】如何清除霓虹灯中除第一个非零车道之外的所有车道?【英文标题】:How to clear all but the first non-zero lane in neon? 【发布时间】:2018-07-08 06:36:07 【问题描述】:

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

如何在 arm neon 中更有效地做到这一点?

【问题讨论】:

所以您想将除第一个非零车道之外的所有车道都归零?它们总是 0 / -1(您可以直接用作 AND 或 AND-NOT 掩码),还是第一个或后面的元素可能有其他非零值? 请注意,您可以简化示例实现,以省略已使用 if() 检查的归零元素。 是的,值只有 0-1,我想最终得到一个只有一个设置为 -1 的霓虹灯寄存器(不一定必须是第一个一)。 @PeterCordes clearmask 简化,并添加了匹配的 neon impl 【参考方案1】:

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) 将归零。

如果其前面的任何元素(ab)非零,则将第二高元素(c)归零。以此类推。


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

【讨论】:

你能解释一下那里发生了什么吗?另外,输出是什么?我认为它必须是q0,但最后两个vand 指令似乎毫无意义,因为它们没有触及q0 @Pavel d1 是 q0 的上半部分。是的,q0 既是输入又是输出。 我将添加aarch64 版本。这应该更容易理解,尽管时间更长且效率更低。 这非常有效。我需要在纸上画画才能看到发生了什么,但它确实有效:) 顺便说一下,对于 arm64,neon intrisic 版本似乎是produce better results:产生相同数量的指令,但它使用的寄存器更少(不触摸v4v31) 不错的算法,我知道我的答案看起来很笨拙,但我没有立即看到这个逻辑。添加了对逻辑的解释。 (我不得不查找一些 ARM 指令以了解他们做了什么来查看您正在创建的掩码。)顺便说一句,x86 版本可以使用 palignr 做同样的事情,这与 vext.8 完全相同。 【参考方案2】:

我的想法与您未注释的代码几乎相同:将反转的元素广播为 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。 (将一个元素与自身进行与运算等效于与-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

【讨论】:

这似乎不会产生比我发布的幼稚 impl 更好的代码。查看 Jake 的解决方案,不确定它是如何工作的,但它产生的操作码减少了两倍。 @Pavel:您是否正在查看包含加载常量的独立版本?我的回答解释说,只有当它可以内联到循环中并将常量设置提升到循环之外时才好;从 asm 应该很明显。不过,Jake 的实现更好。 是的,这是我在函数结束时需要做的一次性事情。我可能会在手臂上做这部分,但我需要移动到手臂,然后回到霓虹灯,我试图通过清除面具使其在霓虹灯中更好。

以上是关于如何清除霓虹灯中除第一个非零车道之外的所有车道?的主要内容,如果未能解决你的问题,请参考以下文章

删除 VIM 中除第一列之外的所有内容

如何清除向量中的所有元素,除了C++中向量中的最后一个元素

选择除第一个之外的所有“tr”

jQuery删除除第一行之外的所有表行

MySQL - 查询除第一行之外的所有行[重复]

如何删除数组索引中除第0条以外的所有记录?