如何用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 theuint64_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
- Set 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
vzipis 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
vpbitto directly gather specific bits across the vector, reducing the number of operations needed.
内容的提问来源于stack exchange,提问作者Pavel P
相关产品推荐
相关产品推荐

