C++ 在单臂neon寄存器中有效地将8位数字扩展到12位
我在neon寄存器中加载了4个字节。如何有效地将其转换为12位,例如,我需要在第一个字节后插入4个零位,在第二个字节后插入8个零位,依此类推。例如,如果我有这4个十六进制字节: 01 02 03 04 : 01 20 00 03 40 表示为简单c函数的相同操作,该函数对表示4个输入字节的32位变量进行操作:C++ 在单臂neon寄存器中有效地将8位数字扩展到12位,c++,arm,intrinsics,neon,C++,Arm,Intrinsics,Neon,我在neon寄存器中加载了4个字节。如何有效地将其转换为12位,例如,我需要在第一个字节后插入4个零位,在第二个字节后插入8个零位,依此类推。例如,如果我有这4个十六进制字节: 01 02 03 04 : 01 20 00 03 40 表示为简单c函数的相同操作,该函数对表示4个输入字节的32位变量进行操作: uint64_t expand12(uint32_t i) { uint64_t r = (i & 0xFF); r |= ((i & 0x0000ff00
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;
}
注意,这是伪代码,我需要在neon寄存器中得到结果
如果这很重要的话,在我的代码中,我有一个函数可以在4个uint16x8\t寄存器中查找max元素的索引。在该函数中,这四个寄存器是
vand
ed,最大元素在所有通道上复制,然后结果是vorr
ed,位掩码{1您必须跳出框框。不要坚持数据类型和位宽度
uint32_t
就是一个由4个uint8_t
组成的数组,在加载过程中,您可以轻松地通过vld4
进行传播
因此,问题变得更容易处理
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
**在x86-64和BMI2上,您可能需要将.dc.b
更改为.byte
在叮当声中,在Intel上,您可以使用标量进行32->48位,1 uop,但在当前的AMD上速度较慢。在带NEON的ARM上,我认为我们可能需要字节移位+每元素移位,可能需要可变计数移位?可能需要shuffle/shift/shuffle来获得零在相关字节的前4位?可能有类似于Jake在其他问题中建议的移位插入。可能我可以做类似的事情。我将用相关信息更新问题01 02 03 04
如何再次变成01 20 00 03 40
?我没有从您的代码片段中看到它,这是支持我想你的意思是:0x02
->0x20
,0x03
->0x0300
,0x04
->0x4000
。我猜你的意思是01 20 00 03 00 40
“转换成12位”是什么意思平均值?最后四位移位12给出一个16位的值。将其视为在四个字节之间插入4-0位。要查看结果,即使是5年前的gcc在-O1
处展开循环并out.val[3]=vdupq\u n\u u8(0)
编译器比你想象的要聪明得多。即使10年前neon Intrinsic完全是垃圾,它们仍然可以很好地完成这一基本部分。当然out.val[3]
在循环之外。temp1+=in.val[1]>>4
没有转化为vsra
,但是vsra
可能并不一定更好:编译器总是知道所有的数据依赖项和操作码计时,并且能够很好地优化它,特别是当函数很大时。基本上,函数的输入已经被交错,如果这有关系的话,就被设置在盒子之外s、 @Pavel你的问题更新真的越来越乏味了。看起来,你的最后两个问题实际上是一个问题。谁知道接下来会发生什么?为什么你不从一开始就要求一个算法来完成这两个任务?为什么你从来没有提过问题的总规模?即使我的客户为一个函数付了几块钱,也不是吗不体贴。是的,我同意,很抱歉:)当我无意中发现合并4个结果的实际问题时,我只提出了这一部分问题。
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]);
}
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;
}
}
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
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