我在 NEON 寄存器中加载了4个字节。我如何有效地将其转换为12位我需要在第一个字节后插入4个零位,在第二个字节后插入8个零位,依此类推。例如,如果我用十六进制表示这4个字节:
It would end up with this in hex:
表示为简单c函数的相同操作对32位变量进行操作,该变量代表4个输入字节:
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
NEON 寄存器中有这些字节,那么用 NEON 实现相同操作以使相同寄存器以这些移位值结束的好方法是什么?请注意,如果有任何帮助的话,所有四个字节的前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
编码,并且在所有通道上都复制了max元素,然后将结果与位掩码vorr
进行{1<<15, 1<<14, ... 1<<0}
编码;然后,我将所有通道和clz进行成对相加,得到每个寄存器的max元素索引。所有这些我都需要在元素之间插入额外的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]);
}
该函数是大型函数的一部分,该函数在一个循环中执行此计算数百万次,并且使用了该函数的结果,并且必须将其存储在氖寄存器中。如果不清楚含义,则将其视为描述算法的伪代码:这意味着仅算法重要,无需优化负载或存储
最佳答案
您必须开箱即用。不要坚持数据类型和位宽。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
关于c++ - 在单 ARM NEON 寄存器中将8位数字有效扩展到12位,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/50032233/