浮点计算单元(FPU)的测试。先还是用前面那个程序,修改了一下,避免了中间的double/single类型转换:
- uint32_t test(int32_t *data, uint32_t size)
- {
- uint32_t i;
-
- float diff=0.0;
- for(i=0; i< size; i++)
- {
- float angle=data[i]/3.4178e+7f;
- float x = cosf(angle);
- float y = sinf(angle);
- float x2 = x*x;
- float y2 = y*y;
- float c= x2+y2;
- diff += fabsf(c - 1.0f);
- }
- return diff*1e6f;
- }
在默认编译指令下,上面的代码不产生浮点指令,而是调用gcc的软浮点程序来计算的:
- 10000440 <test>:
- 10000440: e92d 41f0 stmdb sp!, {r4, r5, r6, r7, r8, lr}
- 10000444: 2900 cmp r1, #0
- 10000446: d032 beq.n 100004ae <test+0x6e>
- 10000448: 4604 mov r4, r0
- 1000044a: 2500 movs r5, #0
- 1000044c: eb00 0881 add.w r8, r0, r1, lsl #2
- 10000450: f854 0b04 ldr.w r0, [r4], #4
- 10000454: f000 fe9e bl 10001194 <__aeabi_i2f>
- 10000458: 4916 ldr r1, [pc, #88] ; (100004b4 <test+0x74>)
- 1000045a: f000 f9f7 bl 1000084c <__aeabi_fdiv>
- 1000045e: 4607 mov r7, r0
- 10000460: f000 ffd8 bl 10001414 <cosf>
- 10000464: 4606 mov r6, r0
- 10000466: 4638 mov r0, r7
- 10000468: f001 f812 bl 10001490 <sinf>
- 1000046c: 4631 mov r1, r6
- 1000046e: 4607 mov r7, r0
- 10000470: 4630 mov r0, r6
- 10000472: f000 fbcf bl 10000c14 <__aeabi_fmul>
- 10000476: 4639 mov r1, r7
- 10000478: 4606 mov r6, r0
- 1000047a: 4638 mov r0, r7
- 1000047c: f000 fbca bl 10000c14 <__aeabi_fmul>
- 10000480: 4601 mov r1, r0
- 10000482: 4630 mov r0, r6
- 10000484: f000 f876 bl 10000574 <__aeabi_fadd>
- 10000488: f04f 517e mov.w r1, #1065353216 ; 0x3f800000
- 1000048c: f000 fcec bl 10000e68 <__aeabi_fsub>
- 10000490: f020 4100 bic.w r1, r0, #2147483648 ; 0x80000000
- 10000494: 4628 mov r0, r5
- 10000496: f000 f86d bl 10000574 <__aeabi_fadd>
- 1000049a: 4544 cmp r4, r8
- 1000049c: 4605 mov r5, r0
- 1000049e: d1d7 bne.n 10000450 <test+0x10>
- 100004a0: 4905 ldr r1, [pc, #20] ; (100004b8 <test+0x78>)
- 100004a2: f000 fbb7 bl 10000c14 <__aeabi_fmul>
- 100004a6: f000 f84d bl 10000544 <__aeabi_f2uiz>
- 100004aa: e8bd 81f0 ldmia.w sp!, {r4, r5, r6, r7, r8, pc}
- 100004ae: 4608 mov r0, r1
- 100004b0: e8bd 81f0 ldmia.w sp!, {r4, r5, r6, r7, r8, pc}
- 100004b4: 4c0260f4
- 100004b8: 49742400
上面反汇编中没有一条是FPU的指令。形如 __aeabi_fmul() 这样的函数是gcc库函数,实现浮点运算。sinf() 和 cosf() 两个函数是C标准库 libm.a 中的。
如何让GCC生成FPU的指令呢?搜了下,在
http://gcc.gnu.org/onlinedocs/gcc/ARM-Options.html 列出了ARM平台的特定选项,其中 -mfpu=xxx 指定使用FPU,不过还没完,还需要 -mfloat-abi=... 指定浮点参数如何传递。结合
https://en.wikipedia.org/wiki/ARM_architecture 的信息,Cortex-M4F对应的FPU应该是 fpv4-sp-d16,所以在GCC编译选项中增加
-mfpu=fpv4-sp-d16 以及
-mfloat-abi=softfp, 然后可以看到编译代码变化:
- 10000440 <test>:
- 10000440: b538 push {r3, r4, r5, lr}
- 10000442: ed2d 8b06 vpush {d8-d10}
- 10000446: b389 cbz r1, 100004ac <test+0x6c>
- 10000448: ed9f 9a19 vldr s18, [pc, #100] ; 100004b0 <test+0x70>
- 1000044c: ed9f aa19 vldr s20, [pc, #100] ; 100004b4 <test+0x74>
- 10000450: 4604 mov r4, r0
- 10000452: eb00 0581 add.w r5, r0, r1, lsl #2
- 10000456: eef7 9a00 vmov.f32 s19, #112 ; 0x70
- 1000045a: ecf4 8a01 vldmia r4!, {s17}
- 1000045e: eef8 8ae8 vcvt.f32.s32 s17, s17
- 10000462: eec8 8a8a vdiv.f32 s17, s17, s20
- 10000466: ee18 0a90 vmov r0, s17
- 1000046a: f000 fe95 bl 10001198 <cosf>
- 1000046e: ee08 0a10 vmov s16, r0
- 10000472: ee18 0a90 vmov r0, s17
- 10000476: f000 fecd bl 10001214 <sinf>
- 1000047a: ee06 0a90 vmov s13, r0
- 1000047e: ee66 7aa6 vmul.f32 s15, s13, s13
- 10000482: 42ac cmp r4, r5
- 10000484: eee8 7a08 vfma.f32 s15, s16, s16
- 10000488: ee77 7ae9 vsub.f32 s15, s15, s19
- 1000048c: eef0 7ae7 vabs.f32 s15, s15
- 10000490: ee39 9a27 vadd.f32 s18, s18, s15
- 10000494: d1e1 bne.n 1000045a <test+0x1a>
- 10000496: eddf 7a08 vldr s15, [pc, #32] ; 100004b8 <test+0x78>
- 1000049a: ee29 9a27 vmul.f32 s18, s18, s15
- 1000049e: eebc 9ac9 vcvt.u32.f32 s18, s18
- 100004a2: ee19 0a10 vmov r0, s18
- 100004a6: ecbd 8b06 vpop {d8-d10}
- 100004aa: bd38 pop {r3, r4, r5, pc}
- 100004ac: 4608 mov r0, r1
- 100004ae: e7fa b.n 100004a6 <test+0x66>
- 100004b0: 00000000
- 100004b4: 4c0260f4
- 100004b8: 49742400
除了 sinf() 和 cosf() 两个函数没有直接对应的浮点指令的外,其它运算都直接翻译成FPU的指令了。这两个函数在 libm.a 当中,软件算法也是要使用很多的浮点计算的,那么它用指令还是用模拟实现呢?查看下arm-gcc的目录树,发现 libm.a 这个文件有很多个,在不同的子目录下:
Cortex-M4F 对应的 ARM 版本是 ARM-v7e-m, 故有三个 libm.a 对应,分别是默认的、softfp目录下的和fpu目录下的。softfp 和 hard (fpu)是不同的ABI模式,就是参数传递约定不同,不能通用,所以分开成两个库了。默认的那个数学库,应该是不使用浮点处理器的。好,GCC是如何选择使用哪个库来连接呢?我发现是根据 -mfpu 和 -mfloat-abi 选项的。如果直接调用 ld 程序来连接,就要自己选择库文件了。
比较下几种方式的平均执行周期:数值越小执行越快
| 一般运算用库函数 | 一般运算用FPU |
库函数软件模拟 | 54115359 | 48338666 |
库函数用FPU | 12504408 | 6730899 |
可发现即使数学库函数用FPU进行浮点运算,在代码中直接用浮点指令而不经过函数调用能节省很多机器周期。
从ST网站下载的软件开发包里面,有CMSIS的数学库:arm_math. 而且提供了源程序。不妨把其中的 arm_sin_f32() 和 arm_cos_f32() 两个函数拿出来试试。这两个函数计算三角函数是用查表加插值的方法。我对比发现虽然它快,计算误差也比GCC的函数大了很多,不要求很准确才敢用啊。
在不用FPU的条件下,平均执行周期为 27879682; 用了FPU以后变为 2326043——程序获得超过十倍的加速。
总结:需要单精度浮点运算的时候,使用STM32F413 Cortex-M4F处理器中的FPU是可以极大提高计算能力。没有FPU的时候,浮点计算开销一部分是软件模拟算法的,一部分是运算函数调用产生的。CMSIS的DSP数学库有许多巧妙的算法可以去发掘,牺牲精度,换来更短的执行时间。
本帖最后由 cruelfox 于 2016-12-26 09:33 编辑