在单臂氖寄存器中有效地将8位数扩展到12位

时间:2018-04-25 22:31:05

标签: c++ arm intrinsics neon

我在氖寄存器中加载了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]);
}

此函数是大函数的一部分,它在循环中执行此计算数百万次,并且使用此函数的结果并且必须在氖寄存器中。将其视为描述算法的伪代码,如果它不清楚这意味着什么:它意味着只有算法很重要,没有需要优化的负载或存储

1 个答案:

答案 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

相关问题