我想知道是否有机会提高性能 这种压缩。这个想法是使价值高于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 周期。
问题是:
一些代码示例将是非常欢迎,因为我是完全noob的时候 来到NEON。
答案 0 :(得分:5)
以下是答案:
是的,它会非常快。
您应该不惜一切代价避免使用内在函数。这不值得付出努力。去集会
我到家后会给你一个示例实施。
/////////////////////////////////////////////// /////
好的,这里有: 您想要将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
请注意智能寄存器分配和循环控制可以节省多少周期和带宽 - 内在函数根本不可能实现。
此实现将如此快速地运行,就像专用硬件一样。
玩得开心!
/////////////////////////////////////////////// ///////
好的,下面是解包功能:
/*
* 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
调整点数:
然后保证所有与内存相关的指令对齐。 4个传播的256位,3个传播的64位如下:
vld4.16 {d16,d18,d20,d22},[pSrc,:256]!
...
vst3.16 {d0,d2,d4},[pDst,:64]!
...
计算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位加载和存储,并且它会更快。