我在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;
}
如何在臂霓虹灯中更有效地做到这一点?
答案 0 :(得分:2)
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
)的前面任何元素(a
或b
)都不为零,则将其置零。依此类推。
在保证元素为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