将缓冲区中的数据压缩为每个元素16位到12位

时间:2014-06-17 07:54:28

标签: c arm simd neon

我想知道是否有机会提高性能 这种压缩。这个想法是使价值高于4095 并将每12位的每个值放在新的连续缓冲区中。就像那样:

概念

  

转换:

     

输入缓冲区:[0.0] [0.1] [0.2] ... [0.15] | [1.0] [1.1] [1.2] ...... [1.15]   | [2.0] [2.1] [2.2] ...... [2.15]等...

     

为:

     

输出缓冲区:[0.0] [0.1] [0.2] ... [0.11] | [1.0] [1.1] [1.2] ...... [1.11]   | [2.0] [2.1] [2.2] ...... [2.11]等...

输入和输出缓冲区定义为:

  

uint16_t input [76800](字节大小等于153600字节)

     

uint24_t输出[38400](字节大小等于115200字节)

所以我将数据大小减少了1/4。这种计算在Cortex-A9上的成本约为1ms,CPU速度为792 MHz,内核为2。 我必须执行这样的“压缩”,因为我通过以太网传输大约18MB / s,这给了 我巨大的开销。我已经测试了各种压缩算法,如Snappy,LZ4,但没有一个 在饱和度和位漂移时甚至接近达到1 ms。

我写了以下代码:

#pragma pack(push, 1)
typedef struct {
        union {
                struct {
                        uint32_t value0_24x1:24;
                };
                struct {
                        uint32_t value0_12x1:12;
                        uint32_t value1_12x1:12;
                };
                struct {
                        uint32_t value0_8x1:8;
                        uint32_t value1_8x1:8;
                        uint32_t value3_8x1:8;
                };
        };
} uint24_t;
#pragma pack(pop)


static inline uint32_t __attribute__((always_inline)) saturate(uint32_t value)
{
        register uint32_t result;

        asm volatile("usat %0, %2, %1 \n\t"                     \
                : [result] "=r" (result)                        \
                : [value] "r" (value), [saturate] "I" (12)      \
                :                                               \
                );

        return result;
}

void __attribute__((noinline, used)) compact(const uint16_t *input, uint24_t *output, uint32_t elements)
{
#if 0
        /* More readable, but slower */
        for (uint32_t i = 0; i < elements; ++i) {
                output->value0_12x1 = saturate(*input++);
                (output++)->value1_12x1 = saturate(*input++);
        }
#else
        /* Alternative - less readable but faster */
        for (uint32_t i = 0; i < elements; ++i, input += 2)
                (output++)->value0_24x1 = saturate(*input) | ((uint32_t)saturate(*(input+1))) << 12;
#endif
}

static uint16_t buffer_in[76800] = {0};
static uint24_t buffer_out[38400] = {0};

int main()
{
    /* Dividing by 2 because we process two input values in a single loop inside compact() */
    compact(buffer_in, buffer_out, sizeof(buffer_in) / sizeof(buffer_in[0]) / 2);

    return 0;
}

这是大会:

248 00008664 <compact>:
249     8664:   e92d4010    push    {r4, lr}
250     8668:   e3a03000    mov r3, #0
251     866c:   ea00000c    b   86a4 <compact+0x40>
252     8670:   e1d040b0    ldrh    r4, [r0]
253     8674:   e6ec4014    usat    r4, #12, r4
254     8678:   e1d0c0b2    ldrh    ip, [r0, #2]
255     867c:   e6ecc01c    usat    ip, #12, ip
256     8680:   e184c60c    orr ip, r4, ip, lsl #12
257     8684:   e2833001    add r3, r3, #1
258     8688:   e2800004    add r0, r0, #4
259     868c:   e5c1c000    strb    ip, [r1]
260     8690:   e7e7445c    ubfx    r4, ip, #8, #8
261     8694:   e7e7c85c    ubfx    ip, ip, #16, #8
262     8698:   e5c14001    strb    r4, [r1, #1]
263     869c:   e5c1c002    strb    ip, [r1, #2]
264     86a0:   e2811003    add r1, r1, #3
265     86a4:   e1530002    cmp r3, r2
266     86a8:   1afffff0    bne 8670 <compact+0xc>
267     86ac:   e8bd8010    pop {r4, pc}

使用GCC 4.6.3编译以下CFLAGS:

  

-Os(-O2和-O3没有任何明显的改进)

     

-march = armv7-a -mcpu = cortex-a9 -mtune = cortex-a9

     

-marm -mfloat-abi = softfp -mfpu = neon funsafe-math-optimizations

基准测试表明,我们每 1 数据转换使用 ~10.3 周期。

问题是:

  1. 我可以使用NEON来提高性能吗?
  2. 有人可以给我一些关于NEON的提示吗?我应该使用什么内在函数?
  3. 一些代码示例将是非常欢迎,因为我是完全noob的时候 来到NEON。

3 个答案:

答案 0 :(得分:5)

以下是答案:

  1. 是的,它会非常快。

  2. 您应该不惜一切代价避免使用内在函数。这不值得付出努力。去集会

  3. 我到家后会给你一个示例实施。

    /////////////////////////////////////////////// /////

    好的,这里有: 您想要将16位打包到12位。它的比例为4:3。

    因此,加载数据4传播并存储3个传播是明智的:vld4.16 - &gt; vst3.16

    /*
    *   void fanic_pack16to12(unsigned short * pDst, unsigned short * pSrc, unsigned int count);
    *   assert :
    *       count >= 64
    *       count % 4 == 0
    *
    *   written by : Jake Lee
    *   part of FANIC project - Fastest ARM NEON Implementation Challenge
    */
        pDst .req r0
        pSrc .req r1
        count .req r2
    
        .text
        .arm
        .global fanic_pack16to12:
    
        .func
        .align 5
    fanic_pack16to12:
        pld     [pSrc]
        pld     [pSrc, #64]
        pld     [pSrc, #128]
        pld     [pSrc, #192]
        pld     [pSrc, #256]
        sub     count, count, #64
    
        .align 5
    1:
        vld4.16     {d16, d18, d20, d22}, [pSrc]!
        vld4.16     {d17, d19, d21, d23}, [pSrc]!
        vld4.16     {d24, d26, d28, d30}, [pSrc]!
        vld4.16     {d25, d27, d29, d31}, [pSrc]!
        pld     [pSrc, #128]
        pld     [pSrc, #192]
        subs    count, count, #64
    
        vqshl.u16   q0, q8, #4
        vqshl.u16   q3, q9, #4
        vqshl.u16   q8, q10, #4
        vqshl.u16   q9, q11, #4
            vqshl.u16   q10, q12, #4
            vqshl.u16   q13, q13, #4
            vqshl.u16   q14, q14, #4
            vqshl.u16   q15, q15, #4
        vshl.u16    q1, q3, #4
        vshl.u16    q2, q8, #8
            vshl.u16    q11, q13, #4
            vshl.u16    q12, q14, #8
        vsri.16     q0, q3, #12
        vsri.16     q1, q8, #8
        vsri.16     q2, q9, #4
            vsri.16     q10, q13, #12
            vsri.16     q11, q14, #8
            vsri.16     q12, q15, #4
    
        vst3.16     {d0, d2, d4}, [pDst]!
        vst3.16     {d1, d3, d5}, [pDst]!
        vst3.16     {d20, d22, d24}, [pDst]!
        vst3.16     {d21, d23, d25}, [pDst]!
        bpl     1b
    
        cmp     count, #-64
        add     pDst, pDst, count
    
        bxle    lr
    
        add     pSrc, pSrc, count, lsl #1
        add     pDst, pDst, count, asr #1
        b       1b
         .endfunc
         .end
    

    请注意智能寄存器分配和循环控制可以节省多少周期和带宽 - 内在函数根本不可能实现。

    此实现将如此快速地运行,就像专用硬件一样。

    • 绝对没有管道危险。
    • 大约50次循环/迭代 =小于1个周期/数据

    玩得开心!

    /////////////////////////////////////////////// ///////

    好的,下面是解包功能:

    /*
    *   void fanic_unpack12to16(unsigned short *pDst, unsigned short *pSrc, unsigned int count);
    *   assert :
    *       count >=64
    *       count % 4 == 0
    *   
    *   written by : Jake Lee
    *   part of FANIC project - Fastest ARM NEON Implementation Challenge
    */
        pDst .req r0
        pSrc .req r1
        count .req r2
    
        .text
        .arm
        .global fanic_unpack12to16:
    
        .func
        .align 5
    fanic_unpack12to16:
    
        pld [pSrc]
        pld [pSrc, #64*1]
        pld [pSrc, #64*2]
        vpush       {q4}
        pld [pSrc, #64*3]
        vmov.i16    q4, #0x0fff
        pld [pSrc, #64*4]
        sub count, count, #64
    
        .align 5
    1:
        vld3.16     {d20, d22, d24}, [pSrc]!
        vld3.16     {d21, d23, d25}, [pSrc]!
        vld3.16     {d26, d28, d30}, [pSrc]!
        vld3.16     {d27, d29, d31}, [pSrc]!
        pld     [pSrc, #128]
        pld     [pSrc, #192]
        subs    count, count, #64
    
        vshr.u16    q1, q11, #8
        vshr.u16    q2, q12, #12
        vshr.u16    q0, q10, #4
        vand        q3, q12, q4
            vshr.u16    q9, q14, #8
        vsli.16     q1, q10, #8
        vsli.16     q2, q11, #4
            vshr.u16    q10, q15, #12
            vsli.16     q9, q13, #8
        vbic.i16    q1, q1, #0xf000
        vbic.i16    q2, q2, #0xf000
        vsli.16     q10, q14, #4
        vshr.u16    q8, q13, #4
        vbic.i16    q9, q9, #0xf000
        vand        q11, q15, q4
        vbic.i16    q10, q10, #0xf000
    
        vst4.16     {d0, d2, d4, d6}, [pDst]!
        vst4.16     {d1, d3, d5, d7}, [pDst]!
        vst4.16     {d16, d18, d20, d22}, [pDst]!
        vst4.16     {d17, d19, d21, d23}, [pDst]!
        bpl     1b
    
        cmp     count, #-64
        add     pSrc, pSrc, count
    
        vpople      {q4}
        bxle    lr
    
        add     pSrc, pSrc, count, asr #1
        add     pDst, pDst, count, lsl #1
        b       1b
    
        .endfunc
        .end
    

    调整点数:

    • 强制对齐src和dst为64字节以获得最大带宽 效率
    • 然后保证所有与内存相关的指令对齐。 4个传播的256位,3个传播的64位如下:

      vld4.16 {d16,d18,d20,d22},[pSrc,:256]!

      ...

      vst3.16 {d0,d2,d4},[pDst,:64]!

      ...

    • 计算64的倍数。否则,您必须编写处理残留数据的额外代码(当前的代码会因对齐错误而崩溃)

    • 您可以将pld偏移增加/减少64,以提高缓存命中率

    如果不是很大,这将大大提高性能。

答案 1 :(得分:1)

最近我编写了使用SSE将16位数据打包成10位的代码。这是the code。我现在没有霓虹灯所以我现在无法将SSE代码重写为NEON。

我使用了以下来源:

重写代码的提示如下:

  • 首先编写转储NEON变量的函数并将其用于调试

  • 使用NEON方式加载和存储变量:

int16x8_t s;
s = vld1q_s16(ptr);
vst1q_s16(s, dst);
  • 您可以从int16x8_t转换为uint32x4_t。

  • 饱和度:

const int16x8_t shft0 = { 4, 4, 4, 4, 4, 4, 4, 4 };
const int16x8_t shft1 = { -4, -4, -4, -4, -4, -4, -4, -4 };
s0 = vrshlq_s16(s, shft0);
s1 = vrshlq_s16(s, shft1);
  • 位移:
uint32x4_t vrshlq_u32 (uint32x4_t, int32x4_t)  // _mm_srli_epi32
uint64x1_t vrshl_u64 (uint64x1_t, int64x1_t)   // _mm_srli_epi64

答案 2 :(得分:0)

程序集看起来很紧,但你可以看到你正在使用16位加载(ldrh)并存储为字节(strb)。您的ARM本机字大小版本是32位,因此真正的问题可能是输入和输出到内存。

您应该重构代码以执行32位加载和存储,并且它会更快。