我在氖寄存器中加载了4个字节。如何有效地将其转换为12位,例如我需要在第一个字节后插入4个零位,在第二个字节后插入8个零位,依此类推。例如,如果我有十六进制的这4个字节:
01 02 03 04
It would end up with this in hex:
01 20 00 03 40
相同的操作表示为一个简单的c函数,它操作一个代表4个输入字节的32位变量:
uint64_t expand12(uint32_t i)
{
uint64_t r = (i & 0xFF);
r |= ((i & 0x0000ff00) << 4); // shift second byte by 4 bits
r |= ((i & 0x00ff0000) << 8); // shift third byte by 8 bits
r |= (((uint64_t)(i & 0xff000000)) << 12); // 4th by 12
return r;
}
那么,如果我在uint8x8_t
氖寄存器中有这些字节,那么在氖中实现相同操作的好方法是什么,这样相同的寄存器最终会得到这些移位值?
注意,如果这有任何帮助,那么所有四个字节都在前4位中具有零。
更新
在我的情况下,我有4个uint16x8_t寄存器,每个我需要计算所有通道的总和(vaddv_u16
),然后对该总和执行vclz_u16
,然后将这四个和在氖寄存器中组合放置它们12位分开:
uint64_t compute(uint16x8_t a, uint16x8_t b, uint16x8_t c, uint16x8_t d)
{
u16 a0 = clz(vaddv(a));
u16 b0 = clz(vaddv(b));
u16 c0 = clz(vaddv(c));
u16 d0 = clz(vaddv(d));
return (a0 << 36) | (b0 << 24) | (c0 << 12) | (d0);
}
注意,这是伪代码,我需要在neon寄存器中输出结果。
如果这很重要,在我的代码中我有一个函数可以找到4个uint16x8_t寄存器中的max元素索引。在该函数中,这四个寄存器vand
,其中最大元素在所有通道上重复,然后结果是vorr
用位掩码{1<<15, 1<<14, ... 1<<0}
;然后,我成对地添加了所有的通道和clz,这给了我每个寄存器的最大元素的索引。所有这些我需要插入元素之间插入的额外4个零位并存储到氖寄存器。 C中的示例:
void compute(uint16_t *src, uint64_t* dst)
{
uint64_t x[4];
for (int i = 0; i < 4; ++i, src+=16)
{
int max = 0;
for (int j = 0; j < 16; ++j)
{
if (src[j] > src[max])
max = j;
}
x[i] = max;
}
*dst = (x[0] << 36) | (x[1] << 24) | (x[2] << 12) | (x[3]);
}
此函数是大函数的一部分,它在循环中执行此计算数百万次,并且使用此函数的结果并且必须在氖寄存器中。将其视为描述算法的伪代码,如果它不清楚这意味着什么:它意味着只有算法很重要,没有需要优化的负载或存储
答案 0 :(得分:2)
你必须开箱即用。不要坚持数据类型和位宽。
uint32_t
只是一个4 uint8_t
的数组,您可以在加载时轻松地通过vld4
传播。
这个问题变得更容易管理。
void foo(uint32_t *pDst, uint32_t *pSrc, uint32_t length)
{
length >>= 4;
int i;
uint8x16x4_t in, out;
uint8x16_t temp0, temp1, temp2;
for (i = 0; i < length; ++i)
{
in = vld4q_u8(pSrc);
pSrc += 16;
temp0 = in.val[1] << 4;
temp1 = in.val[3] << 4;
temp1 += in.val[1] >> 4;
out.val[0] = in.val[0] | temp0;
out.val[1] = in.val[2] | temp1;
out.val[2] = in.val[3] >> 4;
out.val[3] = vdupq_n_u8(0);
vst4q_u8(pDst, out);
pDst += 16;
}
}
请注意,我省略了剩余交易,如果您展开更深,它会运行得更快。
更重要的是,我在没有考虑过两次的情况下在汇编中编写这个函数,因为我不认为编译器会如此巧妙地管理寄存器,out.val[3]
只在外面被初始化为零一次循环。
我还怀疑temp1 += in.val[1] >> 4;
会转换为vsra
,因为非独立目标操作数的指令属性。谁知道?
编译器很糟糕。
更新:好的,这些代码可以满足您的需求,使用汇编语言编写,适用于两种架构。
aarch32
vtrn.16 q0, q1
vtrn.16 q2, q3
vtrn.32 q0, q2
vtrn.32 q1, q3
vadd.u16 q0, q1, q0
vadd.u16 q2, q3, q2
adr r12, shift_table
vadd.u16 q0, q2, q0
vld1.64 {q3}, [r12]
vadd.u16 d0, d1, d0
vclz.u16 d0, d0 // d0 contains the leading zeros
vmovl.u16 q0, d0
vshl.u32 q1, q0, q3
vpadal.u32 d3, d2 // d3 contains the final result
.balign 8
shift_table:
.dc.b 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00 // 0, 12, 24, 4
aarch64
trn1 v16.8h, v0.8h, v1.8h
trn1 v18.8h, v2.8h, v3.8h
trn2 v17.8h, v0.8h, v1.8h
trn2 v19.8h, v2.8h, v3.8h
trn2 v0.4s, v18.4s, v16.4s
trn1 v1.4s, v18.4s, v16.4s
trn2 v2.4s, v19.4s, v17.4s
trn1 v3.4s, v19.4s, v17.4s
add v0.8h, v1.8h, v0.8h
add v2.8h, v3.8h, v2.8h
adr x16, shift_table
add v0.8h, v2.8h, v0.8h
ld1 {v3.2d}, [x16]
mov v1.d[0], v0.d[1]
add v0.4h, v1.4h, v0.4h
clz v0.4h, v0.4h // v0 contains the leading zeros
uxtl v0.4s, v0.4h
ushl v0.4s, v0.4s, v3.4s
mov v1.d[0], v0.d[1]
uadalp v1.1d, v0.2s // v1 contains the final result
.balign 8
shift_table:
.dc.b 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00 // 0, 12, 24, 4
**您可能需要在Clang中将.dc.b
更改为.byte