揭秘ARM FPU 加速浮点计算
關注+星標公眾號,不錯過精彩內容
轉自?|?Mculover666
引言
筆者接觸嵌入式領域軟件開發以來,幾乎用的都是 ARM Cortex M 內核系列的微控制器。感謝C語言編譯器的存在,讓我不用接觸匯編即可進行開發,但是彷佛也錯過了一些風景,沒有領域到編譯器之美和CPU之美,所以決定周末無聊的休息時間通過尋找資料、動手實驗、得出結論的方法來探索 ARM CPU 架構的美妙,以及C語言編譯器的奧秘。(因為我個人實在是不贊同學校中微機原理類課程的教學方法)。
- ARM探索之旅 01 | ?帶你認識ARM Cortex-M陣營 
- ARM探索之旅 02 | ?ARM Cortex-M 用什么指令集? 
一、浮點數的存儲
浮點數按照 IEEE 754 標準存儲在計算機中,ARM浮點環境是遵循 「IEEE 754-1985」 標準實現的。
IEEE 754 標準規定浮點數的存儲格式有三個域,如圖:
- sign:符號位,0表示正數、1表示負數; 
- exponent:二進制小數的指數值編碼; 
- fraction:二進制小數的有效值編碼; 
具體的編碼規則過多,本文重點不在此,不再展開,感興趣可以閱讀我之前的文章:浮點數在計算機中的存儲 —— IEEE 754標準[1](可點擊閱讀原文查看)。
二、浮點支持軟件庫fplib
1. fplib介紹
ARM Cortex-M處理器中計算浮點數的方式有軟件和硬件兩種。
對于不帶 FPU 的處理器,ARM提供了一個「浮點支持軟件庫」用于計算浮點數:fplib。
fplib提供的 API 以__aeabi開頭,比如:
- __aeabi_fadd:計算兩個float型浮點數(float占4個字節,32位) 
- __aeabi_dadd:計算兩個double型浮點數(double占8個字節,64位) 
- __aeabi_f2d:float型轉為double型 
- __aeabi_d2f:double型轉為float型 
除此之外,fplib庫還提供取余、開方等非常多的浮點數操作函數,如有興趣可以查閱文末我列出的參考文檔[2]。
2. 測試代碼與優化等級
編寫如下測試代碼:
float?a?=?5.625; float?b?=?5.625; float?res_add,?res_sub,?res_mul,?res_div;res_add?=?a?+?b; res_sub?=?a?-?b; res_mul?=?a?*?b; res_div?=?a?/?b;printf("res_add?=?%f\r\n",?res_add); printf("res_sub?=?%f\r\n",?res_sub); printf("res_mul?=?%f\r\n",?res_mul); printf("res_div?=?%f\r\n",?res_div); ?使用這段測試代碼,「編譯器優化等級推薦設置為-O0」,否則聰明的編譯器會直接將結果計算出來編譯到程序中,我們就沒法研究了。
?3. armcc測試結果
這節我們驗證是否ARM使用 fplib 庫來計算浮點數,在設置中關閉FPU:
使用MDK編譯之后,進入調試模式查看反匯編結果。
在反匯編中可以看到,變量a是float類型,所以編譯器分配了一個寄存器用于存儲值:
查看0x080031C4處的值,小端存儲模式下(低位在低地址),變量a的值是0x40B40000,存儲方式符合IEEE 754標準。
再來看看浮點數運算操作的反匯編結果,果然調用fplib庫提供的函數完成浮點數的操作:這里還有一個有趣的小細節,在反匯編中可以看到「使用 %f 占位符打印浮點數時,printf是按照double型傳參的」:
4. arm-none-eabi-gcc測試結果
使用STM32CubeMX生成makeifle工程,修改makeifle中的等級為-O0,設置為軟件浮點計算:另外還需要注意,默認gcc編譯時不支持printf打印浮點數,需要在 makefile 中手動加入以下鏈接選項:
LDFLAGS?+=?-u?_printf_float編譯完成之后進行反匯編(注意文件名):
arm-none-eabi-objdump?-s?-d?build/usart1-fpu-test.elf??>?build/usart1-fpu-test.dis同樣,在反匯編文件中即可找到浮點計算代碼:
三、使用 ARM FPU 加速浮點計算
1. ARM FPU的魅力
FPU(Floating Point Unit,浮點單元)是ARM內核中的硬件外設,用于硬件計算浮點數,要想使用FPU計算浮點數,需要程序和編譯器配合。
- 在程序中使能/開啟FPU硬件外設,「使 FPU 硬件可以正常工作」; 
- 在編譯器中設置使用FPU,編譯器會將所有浮點計算的代碼都編譯為「使用FPU操作指令完成」。 
目前Cortex-M4、Cortex-M7、Cortex-M33、Cortex-M35P、Cortex-M55處理器中都具備FPU硬件。
在上一節中我們使用fplib軟件庫來計算浮點數,但是fplib終歸還是軟件方式,每個計算函數的實現都是通過很多的指令去完成計算,并且最終的程序中還會把函數鏈接進可執行程序,導致程序體積變大。
「ARM FPU的魅力在于,浮點計算可以通過簡單的FPU操作指令去完成,相比之下,不僅計算快,也不會增大程序體積。」
2. 如何使能FPU硬件
ARM Cortex - M4內核中將 FPU 作為協處理器設計的,所以通過設置協處理器訪問控制(CPACR,Co-processor access control register)來控制是否使能FPU。
復位之后CP11=0、CP10=0,默認禁止訪問FPU,因為這是Cortex-M內核的外設,寄存器定義CMSIS-Core中,所以可以直接通過下面這行代碼設置CP11=1、CP10=1來允許訪問FPU:
SCB->CPACR?=?0x00F00000;?//?Enable?the?floating?point?unit?for?full?access無論是STM32 HAL庫還是標準庫,在SystemInit()函數中已經存在使能代碼,通過__FPU_PRESENT和__FPU_USED來控制:
/*?FPU?settings?------------------------------------------------------------*/ #if?(__FPU_PRESENT?==?1)?&&?(__FPU_USED?==?1)SCB->CPACR?|=?((3UL?<<?10*2)|(3UL?<<?11*2));??/*?set?CP10?and?CP11?Full?Access?*/ #endif并且,在頭文件 stm32l431xx.h 中已經使能__FPU_PRESENT宏定義:__FPU_PRESENT宏定義是一直使能的,那么如何來控制FPU的使能呢?
別忘了還有一個宏定義__FPU_USED,這是留給編譯器來控制的!
3. ARMCC編譯器如何開啟FPU
MDK編譯器開啟FPU的方法非常簡單,如圖:在MDK中使能FPU,一方面編譯器會設置宏定義__FPU_USED == 1,不放心的話可以在任意位置添加下面的預處理代碼,分別在使用/不使用的情況編譯一下,查看編譯器輸出結果:
#if?__FPU_USED?==?1 #error?"ok!" #endif另一方面,編譯器在編譯的時候,會將所有的浮點運算都編譯為使用FPU操作指令去完成,比如本文最開始的測試代碼編譯結果如下:
4. gcc編譯器如何開啟FPU
在Makefile中加入以下gcc編譯設置項:
#?fpu FPU?=?-mfpu=fpv4-sp-d16#?float-abi FLOAT-ABI?=?-mfloat-abi=hardABI是應用程序二進制接口(Application Binary Interface),-mfloat-abi用來指定使用哪種方式:
- soft:使用CPU寄存器組+軟件庫(fplib)完成浮點操作; 
- softfp:使用CPU寄存組+FPU硬件+軟件庫完成浮點操作; 
- hard:使用FPU寄存器組+FPU硬件+軟件庫完成浮點操作; 
mfpu選項用來指定FPU架構,具體值可以閱讀我在文末給出的參考文檔,本文所使用的值fpv4-sp-d16,意味著僅僅使能Armv7 FPv4-SP-D16 單精度浮點單元擴展。
同樣,對之前的測試代碼編譯,查看反匯編結果,可以看到使用了浮點操作全部使用了FPU相關指令。
四、使用Julia測試FPU加速性能
1. 測試準備
需要準備一份裸機工程,具有屏幕打點顯示功能和串口打印功能。
參考:STM32CubeMX_17 | 使用硬件SPI驅動TFT-LCD(ST7789)。
2. 移植Julia分形測試代碼
Julia測試是通過計算幾幀Julia分形的數據來測試單精度浮點運算的性能,測試代碼參考正點原子,如下:
/*?Private?user?code?---------------------------------------------------------*/ /*?USER?CODE?BEGIN?0?*/ #define?ITERATION?128?//迭代次數 #define?REAL_CONSTANT?0.285f?//實部常量 #define?IMG_CONSTANT?0.01f?//虛部常量//顏色表 uint16_t?color_map[ITERATION];//縮放因子列表 const?uint16_t?zoom_ratio[]?= {120,?110,?100,?150,?200,?275,?350,?450,600,?800,?1000,?1200,?1500,?2000,?1500,1200,?1000,?800,?600,?450,?350,?275,?200,150,?100,?110, };//初始化顏色表 //clut:顏色表指針 void?InitCLUT(uint16_t?*?clut) {uint32_t?i?=?0x00;uint16_t?red?=?0,?green?=?0,?blue?=?0;for?(i?=?0;i?<?ITERATION;?i++)?{//產生?RGB?顏色值red?=?(i*8*256/ITERATION)?%?256;green?=?(i*6*256/ITERATION)?%?256;blue?=?(i*4*256?/ITERATION)?%?256;//將?RGB888,轉換為?RGB565red?=?red?>>?3;red?=?red?<<?11;green?=?green?>>?2;green?=?green?<<?5;blue?=?blue?>>?3;clut[i]?=?red?+?green?+?blue;} }//產生?Julia?分形圖形 //size_x,size_y:屏幕?x,y?方向的尺寸 //offset_x,offset_y:屏幕?x,y?方向的偏移 //zoom:縮放因子 void?GenerateJulia_fpu(uint16_t?size_x,uint16_t?size_y,uint16_t?offset_x,uint16_t?offset_y,uint16_t?zoom) {uint8_t?i;uint16_t?x,y;float?tmp1,tmp2;float?num_real,num_img;float?radius;for?(y?=?0;?y?<?size_y;?y++)?{for?(x?=?0;?x?<?size_x;?x++)?{num_real?=?y?-?offset_y;num_real?=?num_real?/?zoom;num_img?=?x-offset_x;num_img?=?num_img?/?zoom;i?=?0;radius?=?0;while?((i?<?ITERATION-1)?&&?(radius?<?4))?{tmp1?=?num_real?*?num_real;tmp2?=?num_img?*?num_img;num_img?=?2*num_real*num_img?+?IMG_CONSTANT;num_real?=?tmp1?-?tmp2?+?REAL_CONSTANT;radius?=?tmp1?+?tmp2;i++;}//繪制到屏幕lcd_draw_color_point(x,?y,?color_map[i]);}} }/*?USER?CODE?END?0?*/在main函數中創建一些需要的變量:
??/*?USER?CODE?BEGIN?1?*/uint8_t?zoom_index?=?0;uint32_t?start_time?=?0,?end_time?=?0;/*?USER?CODE?END?1?*/調用初始化函數:
/*?USER?CODE?BEGIN?2?*/ printf("Julia?test?by?Mculover666\r\n");lcd_init();//初始化顏色表 InitCLUT(color_map);/*?USER?CODE?END?2?*/調用測試函數:
/*?Infinite?loop?*/ /*?USER?CODE?BEGIN?WHILE?*/ while?(1) {/*?USER?CODE?END?WHILE?*//*?USER?CODE?BEGIN?3?*/start_time?=?HAL_GetTick();GenerateJulia_fpu(240,?240,?120,?120,?zoom_ratio[zoom_index]);end_time?=?HAL_GetTick();printf("diff?time?is?%d?ms\r\n",?end_time?-?start_time);zoom_index++;if?(zoom_index?>?sizeof(zoom_ratio))?{zoom_index?=?0;}???????????? } /*?USER?CODE?END?3?*/3. 測試結果
使用-O2優化等級,在不開 FPU 的情況下,「顯示一幀平均需要11s左右」:程序大小情況:使用-O2優化等級,在開啟 FPU 的情況下,「顯示一幀平均需要4s左右」:程序大小情況:最后放上好看的Julia分形圖:
五、參考資料
[1] 浮點數在計算機中的存儲 —— IEEE 754標準(https://mculover666.blog.csdn.net/article/details/93382331)
[2] About floating-point support,ARM Keil(https://www.keil.com/support/man/docs/armlib/armlib_chr1358938940990.htm)
[3] Compiler Reference Guide,ARM Keil(https://www.keil.com/support/man/docs/armclang_ref/armclang_ref_chr1392305424052.htm)
[4] ARM Cortex-M3與M4權威指南
●嵌入式專欄精選教程
●精選匯總 | ST工具、下載編程工具
●精選匯總 | 嵌入式軟件設計與開發
●精選匯總 | STM32、MCU、單片機
歡迎關注我的公眾號,回復“加群”按規則加入技術交流群,回復“1024”查看更多內容。
歡迎關注我的視頻號:
點擊“閱讀原文”查看更多分享,歡迎點分享、收藏、點贊、在看。
總結
以上是生活随笔為你收集整理的揭秘ARM FPU 加速浮点计算的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: CEF3:用CEF3实现最简单的浏览器
- 下一篇: 中国新一代人工智能治理原则发布 | 发展
