You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用ARM NEON高效重排并组合16个3位无符号数?

NEON Implementation for 3-bit Element Packing & Reshuffling

Alright, let's tackle this NEON reshuffling and packing problem. You've got a uint8x16_t register filled with 16 3-bit unsigned values, and you need to pack them into a uint64_t following that specific bit-layout from your pseudocode. Here's how to do it efficiently with NEON instructions:

Core Logic Breakdown

First, let's map your pseudocode's bit placement to a clear pattern:

  • The 16 elements are grouped into 4 sets of 4:
    • Set 0: src[0], src[4], src[8], src[12] → occupies bits 47-36 of the uint64_t
    • Set 1: src[1], src[5], src[9], src[13] → occupies bits 35-24
    • Set 2: src[2], src[6], src[10], src[14] → occupies bits 23-12
    • Set 3: src[3], src[7], src[11], src[15] → occupies bits 11-0
  • Each element in a set takes 3 consecutive bits, ordered from the first element (highest bits in the set) to the last (lowest bits).

Portable NEON Implementation (ARMv8-A Compatible)

This version works on any ARMv8-A platform and uses basic NEON vector operations:

#include <arm_neon.h>

void reshuffleCombine(uint8x16_t src, uint64_t* dst) {
    // Optional: Ensure elements only retain lower 3 bits (safe guard even if inputs are <8)
    uint8x16_t masked = vandq_u8(src, vdupq_n_u8(0x7));

    // Split the 16-element vector into 4x4 sub-vectors (lanes)
    uint8x4_t lane0 = vget_low_u8(vget_low_u8(masked)); // src[0-3]
    uint8x4_t lane1 = vget_high_u8(vget_low_u8(masked)); // src[4-7]
    uint8x4_t lane2 = vget_low_u8(vget_high_u8(masked)); // src[8-11]
    uint8x4_t lane3 = vget_high_u8(vget_high_u8(masked)); // src[12-15]

    // Transpose the 4x4 matrix to group elements by their set (0,4,8,12; 1,5,9,13; etc.)
    uint8x4_t t0, t1, t2, t3;
    t0 = vzip1_u8(lane0, lane1);
    t1 = vzip2_u8(lane0, lane1);
    t2 = vzip1_u8(lane2, lane3);
    t3 = vzip2_u8(lane2, lane3);

    uint8x4_t row0 = vzip1_u8(t0, t2); // [src0, src4, src8, src12]
    uint8x4_t row1 = vzip2_u8(t0, t2); // [src1, src5, src9, src13]
    uint8x4_t row2 = vzip1_u8(t1, t3); // [src2, src6, src10, src14]
    uint8x4_t row3 = vzip2_u8(t1, t3); // [src3, src7, src11, src15]

    // Shift vector: each element in a row gets shifted by 9, 6, 3, 0 bits (no overlap)
    const uint16x4_t shift_vec = vcreate_u16(0x0009000600030000);

    // Pack each row into a 12-bit value (addition works here since shifted bits don't overlap)
    uint16x4_t r0 = vmovl_u8(row0);
    r0 = vshlq_u16(r0, shift_vec);
    uint16_t packed0 = vaddvq_u16(r0);

    uint16x4_t r1 = vmovl_u8(row1);
    r1 = vshlq_u16(r1, shift_vec);
    uint16_t packed1 = vaddvq_u16(r1);

    uint16x4_t r2 = vmovl_u8(row2);
    r2 = vshlq_u16(r2, shift_vec);
    uint16_t packed2 = vaddvq_u16(r2);

    uint16x4_t r3 = vmovl_u8(row3);
    r3 = vshlq_u16(r3, shift_vec);
    uint16_t packed3 = vaddvq_u16(r3);

    // Combine all 12-bit chunks into the final 64-bit value
    uint64_t d = 0;
    d |= (uint64_t)packed0 << 36;
    d |= (uint64_t)packed1 << 24;
    d |= (uint64_t)packed2 << 12;
    d |= (uint64_t)packed3;

    *dst = d;
}

Optimized Version (ARMv8.1-A+)

If you're targeting ARMv8.1-A or newer, you can use the vpbit instruction to directly extract and pack bits, which is more efficient:

#include <arm_neon.h>

void reshuffleCombine_v81(uint8x16_t src, uint64_t* dst) {
    uint8x16_t masked = vandq_u8(src, vdupq_n_u8(0x7));

    // Transpose to get elements in the order: [0,4,8,12,1,5,9,13,2,6,10,14,3,7,11,15]
    uint8x16_t transposed = vcombine_u8(
        vcombine_u8(vzip1_u8(vget_low_u8(vget_low_u8(masked)), vget_low_u8(vget_high_u8(masked))),
                    vzip2_u8(vget_low_u8(vget_low_u8(masked)), vget_low_u8(vget_high_u8(masked)))),
        vcombine_u8(vzip1_u8(vget_high_u8(vget_low_u8(masked)), vget_high_u8(vget_high_u8(masked))),
                    vzip2_u8(vget_high_u8(vget_low_u8(masked)), vget_high_u8(vget_high_u8(masked))))
    );

    // Extract each bit position (0,1,2) from all elements
    const uint8x16_t mask_bit0 = vdupq_n_u8(0x01);
    const uint8x16_t mask_bit1 = vdupq_n_u8(0x02);
    const uint8x16_t mask_bit2 = vdupq_n_u8(0x04);

    uint64x2_t bit0 = vpbitq_u64(vdupq_n_u64(0), vreinterpretq_u64_u8(transposed), vreinterpretq_u64_u8(mask_bit0));
    uint64x2_t bit1 = vpbitq_u64(vdupq_n_u64(0), vreinterpretq_u64_u8(transposed), vreinterpretq_u64_u8(mask_bit1));
    uint64x2_t bit2 = vpbitq_u64(vdupq_n_u64(0), vreinterpretq_u64_u8(transposed), vreinterpretq_u64_u8(mask_bit2));

    // Shift and combine all bits into the final 64-bit value
    uint64_t d = 0;
    d |= vgetq_lane_u64(bit0, 0) | (vgetq_lane_u64(bit0, 1) << 16);
    d |= (vgetq_lane_u64(bit1, 0) | (vgetq_lane_u64(bit1, 1) << 16)) << 1;
    d |= (vgetq_lane_u64(bit2, 0) | (vgetq_lane_u64(bit2, 1) << 16)) << 2;

    *dst = d;
}

Key Notes

  • The transpose step using vzip is critical here: it rearranges the elements into the exact groups needed for your pseudocode's bit layout.
  • The optional masking step ensures that even if any input elements have accidental high bits set, they won't corrupt the final result.
  • The ARMv8.1+ version uses vpbit to directly gather specific bits across the vector, reducing the number of operations needed.

内容的提问来源于stack exchange,提问作者Pavel P

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 03:54:27