在线时间4746 小时
UID3441752
注册时间2017-11-21
NXP金币82794
TA的每日心情 | 开心 7 天前 |
---|
签到天数: 301 天 连续签到: 2 天 [LV.8]以坛为家I
管理员
  
- 积分
- 39226
- 最后登录
- 2025-7-18
|
说起浮点运算,相信大家平时一定接触不少。无论你要编写什么项目代码,相信浮点运算都或多或少要被使用,简单来说:即便你是要计算1.0f+1.0f这样一个一眼得到答案的运算。那对于MCU平台来说,这个操作真的是一眼结果吗?答案是否定的。
先来说说MCU对于浮点计算的支持,换句话说,MCU是否能够进行浮点运算呢?这里以Cortex-M系列MCU为例,Cortex-M4处理器中有一个单精度浮点单元(FPU), 能够高效率处理较为复杂的浮点运算,例如电机闭环控制、PID算法、快速傅里叶变换等。而Cortex-M7系列处理器同时具备单精度和双精度浮点运算能力,加速复杂的浮点运算,提高嵌入式系统应用的性能。而针对于,那些不带FPU的处理器,例如Cortex-M0系列,ARM提供了一个浮点支持的软件库,用于计算浮点数。当然,这样一来,由于是软件模拟浮点运算,相较于具有专门的附件硬件单元的处理器,计算效率当然会大大降低。
这样一看,一个小小的浮点运算可真是内涵乾坤啊。本期小编给大家带来的奇妙体验也正是与此有关,由于没有合理使用硬件FPU单元而导致对程序运行时间产生影响。
为了能够更好的说明问题,小编在这里先举两个简单的浮点数计算的例子,并对其进行编译,看看不同的编译选项或者说不同的平台对于所编译的代码的影响。就让我们以Keil的armclang编译器为例进行说明。
以下是参考代码,代码本体很简单,分别针对float以及double类型进行浮点出发运算,将其命名为float_div.c:
- #include "stdlib.h"
- float fp32_div(float a, float b){
- return a / b;
- }
- double d64_div(double a, double b){
- return a / b;
- }
复制代码 调用armclang对其进行编译,先指定平台为Cortex-M7:
- armclang.exe .\float_div.c --target=arm-arm-none-eabi -mcpu=cortex-m7 -mfloat-abi=hard -c -ofast -o .\float_div.o
复制代码 看看汇编代码的样子:
- 00000000 <fp32_div>:
- 0: b082 sub sp, #8
- 2: ed8d 0a01 vstr s0, [sp, #4]
- 6: edcd 0a00 vstr s1, [sp]
- a: ed9d 0a01 vldr s0, [sp, #4]
- e: ed9d 1a00 vldr s2, [sp]
- 12: ee80 0a01 vdiv.f32 s0, s0, s2
- 16: b002 add sp, #8
- 18: 4770 bx lr
- 00000000 <d64_div>:
- 0: b084 sub sp, #16
- 2: ed8d 0b02 vstr d0, [sp, #8]
- 6: ed8d 1b00 vstr d1, [sp]
- a: ed9d 0b02 vldr d0, [sp, #8]
- e: ed9d 1b00 vldr d1, [sp]
- 12: ee80 0b01 vdiv.f64 d0, d0, d1
- 16: b004 add sp, #16
- 18: 4770 bx lr
复制代码 可以看到这里无论是float类型的fp32_div还是double类型的d64_div都调用了类似vdiv.n的汇编指令进行操作。
换一个平台,Cortex-M4:
- armclang.exe .\float_div.c --target=arm-arm-none-eabi -mcpu=cortex-m4 -mfloat-abi=hard -c -ofast -o .\float_div.o
复制代码 继续看下汇编的样子:
- 00000000 <fp32_div>:
- 0: b082 sub sp, #8
- 2: ed8d 0a01 vstr s0, [sp, #4]
- 6: edcd 0a00 vstr s1, [sp]
- a: ed9d 0a01 vldr s0, [sp, #4]
- e: ed9d 1a00 vldr s2, [sp]
- 12: ee80 0a01 vdiv.f32 s0, s0, s2
- 16: b002 add sp, #8
- 18: 4770 bx lr
- 00000000 <d64_div>:
- 0: b580 push {r7, lr}
- 2: b084 sub sp, #16
- 4: ed8d 0b02 vstr d0, [sp, #8]
- 8: ed8d 1b00 vstr d1, [sp]
- c: 9802 ldr r0, [sp, #8]
- e: 9903 ldr r1, [sp, #12]
- 10: 9a00 ldr r2, [sp, #0]
- 12: 9b01 ldr r3, [sp, #4]
- 14: f7ff fffe bl 0 <__aeabi_ddiv>
- 18: ec41 0b10 vmov d0, r0, r1
- 1c: b004 add sp, #16
- 1e: bd80 pop {r7, pc}
复制代码 相信细心的读者已经发现了,d64_div的代码变了,变成了一条__aeabi_ddiv的函数调用。当然,这也正验证了我们刚才说的,Cortex-M4不具备double类型计算的能力,需要调用系统库中所提供的软件模拟函数。尽管我们已经指定了-mfloat-abi=hard,即硬件浮点单元。
不过,到了这里,故事还没有结束,假设我们指定使用软件模拟库呢:
armclang.exe .\float_div.c --target=arm-arm-none-eabi -mcpu=cortex-m7 -mfloat-abi=soft -c -ofast -o .\float_div.o
汇编代码如下:
- 00000000 <fp32_div>:
- 0: b580 push {r7, lr}
- 2: b082 sub sp, #8
- 4: 9001 str r0, [sp, #4]
- 6: 9100 str r1, [sp, #0]
- 8: 9801 ldr r0, [sp, #4]
- a: 9900 ldr r1, [sp, #0]
- c: f7ff fffe bl 0 <__aeabi_fdiv>
- 10: b002 add sp, #8
- 12: bd80 pop {r7, pc}
- 00000000 <d64_div>:
- 0: b580 push {r7, lr}
- 2: b084 sub sp, #16
- 4: 9103 str r1, [sp, #12]
- 6: 9002 str r0, [sp, #8]
- 8: 9301 str r3, [sp, #4]
- a: 9200 str r2, [sp, #0]
- c: 9802 ldr r0, [sp, #8]
- e: 9903 ldr r1, [sp, #12]
- 10: 9a00 ldr r2, [sp, #0]
- 12: 9b01 ldr r3, [sp, #4]
- 14: f7ff fffe bl 0 <__aeabi_ddiv>
- 18: b004 add sp, #16
- 1a: bd80 pop {r7, pc}
复制代码
这样一来,无论是fp32_div还是d64_div都被编译成了调用库的样子。可能大家又开始好奇了,那链接之后会不会被展开呢?我们继续做实验,这次编写一个main函数,并将他们链接在一起:
- #include "stdlib.h"
- float fp32_div(float a, float b);
- double d64_div(double a, double b);
- int main(){
- fp32_div(1.0f, 5.0f);
- d64_div(1.0, 5.0);
- }
复制代码
编译链接,将main.c和刚才生成的float_div.o链接到一起,不过我们这里指定float-abi为hard,即采用硬件浮点单元,看看发生什么:
armclang.exe .\main.c float_div.o --target=arm-arm-none-eabi -mcpu=cortex-m7 -mfloat-abi=hard -ofast -o test
来看看汇编代码:
- 00008148 <fp32_div>:
- 8148: b580 push {r7, lr}
- 814a: b082 sub sp, #8
- 814c: 9001 str r0, [sp, #4]
- 814e: 9100 str r1, [sp, #0]
- 8150: 9801 ldr r0, [sp, #4]
- 8152: 9900 ldr r1, [sp, #0]
- 8154: f000 f81c bl 8190 <_fdiv>
- 8158: b002 add sp, #8
- 815a: bd80 pop {r7, pc}
- 815c: 0000 movs r0, r0
- ...
- 00008160 <main>:
- 8160: b580 push {r7, lr}
- 8162: eeb7 0a00 vmov.f32 s0, #112 ; 0x3f800000 1.0
- 8166: eef1 0a04 vmov.f32 s1, #20 ; 0x40a00000 5.0
- 816a: f7ff ffed bl 8148 <fp32_div>
- 816e: eeb7 0b00 vmov.f64 d0, #112 ; 0x3f800000 1.0
- 8172: eeb1 1b04 vmov.f64 d1, #20 ; 0x40a00000 5.0
- 8176: f7ff ffd7 bl 8128 <d64_div>
- 817a: 2000 movs r0, #0
- 817c: bd80 pop {r7, pc}
- 。。。
- 00008190 <_fdiv>:
- 8190: ee00 0a10 vmov s0, r0
- 8194: ee00 1a90 vmov s1, r1
- 8198: ee80 1a20 vdiv.f32 s2, s0, s1
- 819c: ee11 0a10 vmov r0, s2
- 81a0: 4770 bx lr
复制代码
没错,尽管我们编译库的时候,选用了soft方式,但是链接的时候,由于我们使能了硬件浮点单元,因此链接器还是会将使用了硬件浮点单元的计算库链接到程序中。
当然,我们也可以认为,库中有两个同名函数,都叫_fdiv。只是链接器根据选用的浮点处理方式来链接不同的实现。大家感兴趣的话,也可以看看使用soft类型所链接的_fdiv函数是什么样子,这里只给大家看看部分代码:
- 000084ec <_fdiv>:
- 84ec: f44f 0c7f mov.w ip, #16711680 ; 0xff0000
- 84f0: ea1c 12d0 ands.w r2, ip, r0, lsr #7
- 84f4: bf1e ittt ne
- 84f6: ea1c 13d1 andsne.w r3, ip, r1, lsr #7
- 84fa: ea92 0f0c teqne r2, ip
- 84fe: ea93 0f0c teqne r3, ip
- 8502: f000 8085 beq.w 8610 <_fdiv+0x124>
- 8506: ea90 0f01 teq r0, r1
- 850a: bf48 it mi
- 850c: f442 7280 orrmi.w r2, r2, #256 ; 0x100
- 8510: f440 0c00 orr.w ip, r0, #8388608 ; 0x800000
- 8514: f441 0000 orr.w r0, r1, #8388608 ; 0x800000
- 8518: f02c 417f bic.w r1, ip, #4278190080 ; 0xff000000
- 851c: f020 407f bic.w r0, r0, #4278190080 ; 0xff000000
- 8520: b500 push {lr}
- 8522: eba2 0203 sub.w r2, r2, r3
- 8526: 4281 cmp r1, r0
- 8528: f20f 1c08 addw ip, pc, #264 ; 0x108
- 852c: ebac 4e50 sub.w lr, ip, r0, lsr #17
- 8530: f89e e000 ldrb.w lr, [lr]
复制代码
至此,小编就给大家分享了以下使用浮点计算时候,会遇到的一些小意外。大家在日常开发中,一定要根据所选的MCU类型,设置/使用正确的浮点计算方式,例如:单精度还是双精度。不然,很可能因为调用了本不支持的浮点操作,而增加了CPU loading。
|
|