代码之家  ›  专栏  ›  技术社区  ›  Pavel P

在arm neon中高效地重新排列和组合16个3位数字

  •  0
  • Pavel P  · 技术社区  · 8 年前

    我有一个由16个无符号数字组成的数组,其中每个数字都小于8(例如,它可以由3位表示)。这十六个数字加载到 uint8x16_t q寄存器。我需要对它们进行重组和组合,类似于以下伪代码:

    void reshuffleCombine(uint8_t src[16], uint64_t* dst)
    {
        uint64 d = 0;
    
        d |= uint64(src[0])  << 45;
        d |= uint64(src[4])  << 42;
        d |= uint64(src[8])  << 39;
        d |= uint64(src[12]) << 36;
    
        d |= uint64(src[1])  << 33;
        d |= uint64(src[5])  << 30;
        d |= uint64(src[9])  << 27;
        d |= uint64(src[13]) << 24;
    
        d |= uint64(src[2])  << 21;
        d |= uint64(src[6])  << 18;
        d |= uint64(src[10]) << 15;
        d |= uint64(src[14]) << 12;
    
        d |= uint64(src[3])  << 9;
        d |= uint64(src[7])  << 6;
        d |= uint64(src[11]) << 3;
        d |= uint64(src[15]) << 0;
    
        *dst = d;
    }
    
    void reshuffleCombineNeon(uint8x16_t src, uint64_t* dst)
    {
        uint64x1_t res;
        // ??
        vst1_u64(dst, res);
    }
    

    我可以用1个vld和1个vtbl对它们进行重组,但是,整个操作是最后的步骤之一,不会重复很多次(例如,1个vld不能在多个重组组合之间共享),因此如果可能的话,最好使用vtrn/vzip,并且可能比vld+vtbl更有效。然而,问题的关键是:如何将这16个3位数字合并为一个48位值(存储在64位uint中)。此函数在算法结束时运行,16个3位数字来自neon,函数结果存储在内存中。

    My neon version :

    void reshuffleCombineNeon(uint8x16_t src, uint32_t* dst)
    {
        static const uint8_t idx0[] = { 15, 7, 14, 6, 13, 5, 12, 4 };
        static const uint8_t idx1[] = { 11, 3, 10, 2, 9,  1, 8,  0 };
    
        uint8x8x2_t y;
        y.val[0] = vget_low_u8(src);
        y.val[1] = vget_high_u8(src);
    
        uint8x8_t vidx0 = vld1_u8(idx0);
        uint8x8_t vidx1 = vld1_u8(idx1);
        uint8x8_t x0 = vtbl2_u8(y, vidx0);
        uint8x8_t x1 = vtbl2_u8(y, vidx1);
    
        uint8x8_t x01 = vsli_n_u8(x0, x1, 3);
        uint16x8_t x01L = vmovl_u8(x01);
    
        uint32x4_t x01LL = vsraq_n_u32(vreinterpretq_u32_u16(x01L), vreinterpretq_u32_u16(x01L), 10);
        x01LL = vmovl_u16(vmovn_u32(x01LL));
    
        uint64x2_t x01X = vsraq_n_u64(vreinterpretq_u64_u32(x01LL), vreinterpretq_u64_u32(x01LL), 20);
        x01X = vmovl_u32(vmovn_u64(x01X));
    
        uint64x1_t X0 = vget_low_u64(x01X);
        uint64x1_t X1 = vget_high_u64(x01X);
        X0 = vsli_n_u64(X0, X1, 24);
        vst1_u32(dst, vreinterpret_u32_u64(X0));
    }
    
    1 回复  |  直到 8 年前
        1
  •  2
  •   Jake 'Alquimista' LEE    8 年前

    好吧,不管它值多少钱,以下是根据我所知优化的霓虹灯版本:


    rev64   v16.16b, v16.16b
    usra    v16.2d, v16.2d, #32-3       // 8 elements (8bit)
    ushll   v17.8h, v16.8b, #6
    uaddw2  v16.8h, v17.8h, v16.16b     // 4 elements (16bit)
    uxtl    v16.4s, v16.4h
    usra    v16.2d, v16.2d, #32-12      // 2 elements (32bit)
    ushll2  v17.2d, v16.4s, #24
    uaddw   v16.2d, v17.2d, v16.2s      // 1 element (64bit), d16 contains the result
    

    它应该比你的速度快得多,但同样,在订单机器上也没有多大意义。