C++ 在单臂neon寄存器中有效地将8位数字扩展到12位

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

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