查看: 671|回复: 1

记一次编译浮点函数库的奇妙经历

[复制链接]
  • TA的每日心情
    开心
    7 天前
  • 签到天数: 301 天

    连续签到: 2 天

    [LV.8]以坛为家I

    3868

    主题

    7472

    帖子

    0

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    39226
    最后登录
    2025-7-18
    发表于 2025-2-7 10:46:58 | 显示全部楼层 |阅读模式
    说起浮点运算,相信大家平时一定接触不少。无论你要编写什么项目代码,相信浮点运算都或多或少要被使用,简单来说:即便你是要计算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:
    1. #include "stdlib.h"
    2. float fp32_div(float a, float b){
    3.      return a / b;
    4. }
    5. double d64_div(double a, double b){
    6.      return a / b;
    7. }
    复制代码
    调用armclang对其进行编译,先指定平台为Cortex-M7:
    1. armclang.exe .\float_div.c  --target=arm-arm-none-eabi -mcpu=cortex-m7  -mfloat-abi=hard -c -ofast -o .\float_div.o
    复制代码
    看看汇编代码的样子:
    1. 00000000 <fp32_div>:

    2.     0:    b082       sub sp, #8

    3.     2:    ed8d 0a01   vstr s0,  [sp, #4]

    4.     6:    edcd 0a00   vstr s1,  [sp]

    5.     a:    ed9d 0a01   vldr s0,  [sp, #4]

    6.     e:    ed9d 1a00   vldr s2,  [sp]

    7.    12:    ee80 0a01   vdiv.f32 s0,  s0, s2

    8.    16:    b002       add sp,  #8

    9.    18:    4770       bx   lr

    10. 00000000 <d64_div>:

    11.     0:    b084       sub sp, #16

    12.     2:    ed8d 0b02  vstr d0,  [sp, #8]

    13.     6:    ed8d 1b00  vstr d1,  [sp]

    14.     a:    ed9d 0b02  vldr d0,  [sp, #8]

    15.    e:    ed9d 1b00  vldr d1,  [sp]

    16.   12:    ee80 0b01   vdiv.f64 d0, d0, d1

    17.   16:    b004       add sp, #16

    18.    18:    4770       bx   lr
    复制代码
    可以看到这里无论是float类型的fp32_div还是double类型的d64_div都调用了类似vdiv.n的汇编指令进行操作。


    换一个平台,Cortex-M4:

    1. armclang.exe .\float_div.c  --target=arm-arm-none-eabi -mcpu=cortex-m4  -mfloat-abi=hard -c -ofast -o .\float_div.o
    复制代码
    继续看下汇编的样子:

    1. 00000000 <fp32_div>:

    2.     0:    b082       sub sp, #8

    3.     2:    ed8d 0a01   vstr s0,  [sp, #4]

    4.     6:    edcd 0a00   vstr s1,  [sp]

    5.     a:    ed9d 0a01   vldr s0,  [sp, #4]

    6.     e:    ed9d 1a00   vldr s2,  [sp]

    7.    12:    ee80 0a01   vdiv.f32 s0,  s0, s2

    8.    16:    b002       add sp, #8

    9.    18:    4770       bx   lr

    10. 00000000 <d64_div>:

    11.     0:    b580       push     {r7, lr}

    12.     2:    b084       sub sp, #16

    13.     4:    ed8d 0b02  vstr d0,  [sp, #8]

    14.     8:    ed8d 1b00  vstr d1,  [sp]

    15.    c:    9802       ldr  r0, [sp, #8]

    16.    e:    9903       ldr  r1, [sp, #12]

    17.   10:    9a00       ldr  r2, [sp, #0]

    18.    12:    9b01       ldr  r3, [sp, #4]

    19.    14:    f7ff fffe       bl   0  <__aeabi_ddiv>

    20.    18:    ec41 0b10   vmov    d0,  r0, r1

    21.    1c:    b004       add sp, #16

    22.    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
    汇编代码如下:
    1. 00000000 <fp32_div>:

    2.     0:    b580       push     {r7, lr}

    3.     2:    b082       sub sp, #8

    4.     4:    9001       str  r0, [sp, #4]

    5.     6:    9100       str  r1, [sp, #0]

    6.     8:    9801       ldr  r0, [sp, #4]

    7.     a:    9900       ldr  r1, [sp, #0]

    8.     c:    f7ff fffe       bl   0  <__aeabi_fdiv>

    9.    10:    b002       add sp, #8

    10.    12:    bd80       pop {r7, pc}

    11. 00000000 <d64_div>:

    12.     0:    b580       push     {r7, lr}

    13.     2:    b084       sub sp, #16

    14.     4:    9103       str  r1, [sp, #12]

    15.     6:    9002       str  r0, [sp, #8]

    16.     8:    9301       str  r3, [sp, #4]

    17.     a:    9200       str  r2, [sp, #0]

    18.    c:    9802       ldr  r0, [sp, #8]

    19.    e:    9903       ldr  r1, [sp, #12]

    20.   10:    9a00       ldr  r2, [sp, #0]

    21.    12:    9b01       ldr  r3, [sp, #4]

    22.    14:    f7ff fffe       bl   0  <__aeabi_ddiv>

    23.    18:    b004       add sp, #16

    24.    1a:    bd80       pop {r7, pc}
    复制代码


    这样一来,无论是fp32_div还是d64_div都被编译成了调用库的样子。可能大家又开始好奇了,那链接之后会不会被展开呢?我们继续做实验,这次编写一个main函数,并将他们链接在一起:

    1. #include "stdlib.h"

    2. float fp32_div(float a, float b);

    3. double d64_div(double  a, double b);

    4. int main(){

    5.     fp32_div(1.0f, 5.0f);

    6.     d64_div(1.0, 5.0);

    7. }
    复制代码


    编译链接,将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
    来看看汇编代码:
    1. 00008148 <fp32_div>:

    2.      8148:   b580       push     {r7, lr}

    3.      814a:   b082       sub sp, #8

    4.      814c:    9001       str  r0, [sp, #4]

    5.     814e:   9100       str  r1, [sp, #0]

    6.     8150:   9801       ldr  r0, [sp, #4]

    7.     8152:   9900       ldr  r1, [sp, #0]

    8.     8154:   f000  f81c    bl   8190 <_fdiv>

    9.     8158:   b002       add sp, #8

    10.      815a:   bd80       pop {r7, pc}

    11.      815c:    0000       movs    r0, r0

    12.       ...

    13. 00008160 <main>:

    14.      8160:   b580       push     {r7, lr}

    15.      8162:   eeb7 0a00   vmov.f32     s0,  #112 ; 0x3f800000  1.0

    16.      8166:   eef1 0a04    vmov.f32     s1,  #20  ; 0x40a00000  5.0

    17.      816a:   f7ff ffed      bl   8148  <fp32_div>

    18.     816e:   eeb7 0b00   vmov.f64     d0,  #112      ; 0x3f800000  1.0

    19.     8172:   eeb1  1b04   vmov.f64     d1, #20 ; 0x40a00000  5.0

    20.      8176:   f7ff ffd7      bl   8128  <d64_div>

    21.      817a:   2000       movs    r0, #0

    22.      817c:    bd80       pop {r7, pc}

    23. 。。。

    24. 00008190 <_fdiv>:

    25.      8190:   ee00 0a10   vmov    s0,  r0

    26.      8194:   ee00 1a90   vmov    s1,  r1

    27.      8198:   ee80 1a20   vdiv.f32 s2,  s0, s1

    28.      819c:    ee11 0a10   vmov    r0,  s2

    29.      81a0:   4770       bx   lr
    复制代码

    没错,尽管我们编译库的时候,选用了soft方式,但是链接的时候,由于我们使能了硬件浮点单元,因此链接器还是会将使用了硬件浮点单元的计算库链接到程序中。


    当然,我们也可以认为,库中有两个同名函数,都叫_fdiv。只是链接器根据选用的浮点处理方式来链接不同的实现。大家感兴趣的话,也可以看看使用soft类型所链接的_fdiv函数是什么样子,这里只给大家看看部分代码:
    1. 000084ec <_fdiv>:

    2.      84ec:    f44f 0c7f     mov.w   ip,  #16711680    ; 0xff0000

    3.     84f0:    ea1c  12d0   ands.w  r2, ip, r0, lsr #7

    4.      84f4:    bf1e       ittt  ne

    5.      84f6:    ea1c 13d1   andsne.w     r3,  ip, r1, lsr #7

    6.      84fa:    ea92 0f0c    teqne    r2,  ip

    7.      84fe:    ea93 0f0c    teqne    r3,  ip

    8.      8502:   f000 8085   beq.w   8610  <_fdiv+0x124>

    9.      8506:   ea90 0f01    teq r0,  r1

    10.      850a:   bf48       it    mi

    11.      850c:    f442 7280   orrmi.w r2,  r2, #256  ; 0x100

    12.      8510:   f440 0c00    orr.w     ip,  r0, #8388608 ; 0x800000

    13.      8514:   f441 0000   orr.w     r0,  r1, #8388608 ; 0x800000

    14.      8518:   f02c 417f    bic.w     r1,  ip, #4278190080  ; 0xff000000

    15.      851c:    f020 407f    bic.w     r0,  r0, #4278190080  ; 0xff000000

    16.      8520:   b500       push     {lr}

    17.      8522:   eba2 0203   sub.w    r2,  r2, r3

    18.      8526:   4281       cmp r1, r0

    19.      8528:   f20f 1c08    addw    ip,  pc, #264 ; 0x108

    20.      852c:    ebac 4e50   sub.w    lr,  ip, r0, lsr #17

    21.     8530:   f89e e000    ldrb.w   lr,  [lr]
    复制代码

    至此,小编就给大家分享了以下使用浮点计算时候,会遇到的一些小意外。大家在日常开发中,一定要根据所选的MCU类型,设置/使用正确的浮点计算方式,例如:单精度还是双精度。不然,很可能因为调用了本不支持的浮点操作,而增加了CPU loading。




    qiandao qiandao
    回复

    使用道具 举报

  • TA的每日心情
    开心
    5 小时前
  • 签到天数: 335 天

    连续签到: 5 天

    [LV.8]以坛为家I

    12

    主题

    1094

    帖子

    0

    金牌会员

    Rank: 6Rank: 6

    积分
    2515
    最后登录
    2025-7-18
    发表于 2025-2-7 10:58:20 | 显示全部楼层
    好文,赞
    哎...今天够累的,签到来了~
    回复

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /3 下一条

    Archiver|手机版|小黑屋|恩智浦技术社区

    GMT+8, 2025-7-18 16:48 , Processed in 0.086723 second(s), 22 queries , MemCache On.

    Powered by Discuz! X3.4

    Copyright © 2001-2024, Tencent Cloud.

    快速回复 返回顶部 返回列表