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

10-13 07:38