# C6 p1 Z) ]( u# P% m# t2 Z+ J
01引言' v2 y3 v: u. _6 Z: }
客户在使用 STM32G474 时,希望使用 FPU 进行浮点运算,并最大化其性能。本文 从 STM32G474 系统的角度、ARM DSP Lib、编译选项的影响等几个方面探讨如何提升整体性能,并介绍如何使用 KEIL 工具进行测量。) d2 A# B2 }8 @1 `6 Q0 C) Z
! J3 U' t) w. `% g# ?- d2 B02STM32G474 FPU 运算性能优化
0 E, d9 _8 u' f* ~9 G7 F2.1. STM32G474 系统性能优化 - f1 N; r) B% E1 s
. v+ n. n, x, M5 J# A: \* ]2 FSTM32G474 使用的是 ARM Cortex-M4 内核(+FPU)。一般代码会放在 FLASH 区, 通过 I-Bus 读取。这里 STM32G474 有 FLASH 预取指及 CACHE Line, 无需放入 IRAM 或 CCM。因为 Cortex-M4 DSP 指令中没有运算指令与加载指令并行的混合指令,所以数据 存放区域及 Bus 的选择理论上对性能的影响不大。如下图 1 所示,可将 FPU 运算数据放 在 SRAM1。另外还需尽量避免 SRAM 的并发访问,如使能了 DMA,DMA 传输目的地 可以使用 SRAM2,从而减少潜在的 SRAM 并发访问产生的性能下降。应用则需要根据实 际情况,合理使用内存区域。
( a2 L2 d! r& N6 u7 ]- l d▲ 图1. STM32G474 架构
; _3 b! S( Y: W8 j) V' Q
: Z5 e6 ?, t3 S" x9 s: c& m2.2. ARM DSP Lib 的使用
3 {* L" M+ }7 k E$ ~ b& h- B在 ARM DSP 库实现了很多 math 算法,可进行浮点乘加、点积、卷积、FFT、NN 等 多种算法 API,可以使用 ARM DSP 库高效使用 FPU。ARM DSP 代码位置如下:1 j# ~& N* P0 t$ A
0 E3 F1 f0 W, Y+ j/ U2 b$ I2.3. 示例代码
* E8 B. j8 W2 S$ N+ N; X' k0 ?) V下面示例代码中对浮点乘法运算进行了测试。用户可以使用 STM32CubeMX 生成 STM32G474 KEIL 工程,在 main.c 文件中加入如下示例代码:
# ^: `9 P4 m+ n o- 4 J7 m, x" ?' _, U/ ]' Y! K9 r
- __attribute__((section (".TEST_INPUT_A"))) float32_t testInputA[1024] =
& b( B+ T8 j8 t' ]; E - {
' n& H( I1 h& s1 G - 0.623234f, 0.799049f, 0.940890f, -0.992092f, 0.212035f, 0.237882f, -, I" r: H- G* a: B
- 1.007763f, -0.742045f,4 E& F3 R0 Q2 Z h! y1 P
- ~~ 这里数组使用动态生成的float数据,数据量较大,略
# _# x. ?, y3 z. Q4 K o( q - -0.417470f, -0.205806f, -0.174323f, 0.217577f, 1.684295f, 0.119528f,
1 b+ ]3 x9 |5 U - 0.650667f, 2.080061f0 ]9 t: W, j7 j5 Z( U( z; N4 W
- };& ?) o! \" G% Y# x5 c1 ?( ]7 C
- __attribute__((section (".TEST_INPUT_B"))) float32_t testInputB[1024] = , S/ W% p5 d8 ` F
- {# h4 ]7 d8 e. G5 w, J& B
- -2.423957f, -0.223831f, 0.058070f, -0.424614f, -0.202918f, -1.513077f, -
8 @7 f2 ~6 f# c9 n: [8 ^1 i - 1.126352f, -0.815002f,
5 f8 { v, M F+ P! K8 L: C - % m$ }% z# [1 ~ D U
- ~~ 这里数组使用动态生成的float数据,数据量较大,略
9 f v8 i$ g% _8 ] - -0.447001f, -0.725993f, 0.354045f, -0.506772f, -2.103747f, -0.664684f, 1.450110f, -0.329805f ) K7 W3 l% t3 I! ~2 {
- };
- n3 ~; w+ @1 S, @: {2 k - ; {) G( v5 D! q. w" x" C: [+ X
- __attribute__((section (".TEST_RESULT_D"))) float32_t testResult[1024];
# f1 M& D/ H) I4 P+ C
0 T7 {0 ]2 n' z- W9 b$ L' R& B7 Z- float32_t* pA;3 J# I9 Q, U; Q; v. @; {+ A% n
- float32_t* pB; , b0 |8 \/ D4 J' a/ X
- float32_t* pR; 9 J+ \9 S8 {. Q8 S, r8 Y7 I8 y
- /* Private user code --------------------------------------------------*/ % s0 d0 c) X3 u. L6 q, h6 l
- /* USER CODE BEGIN 0 */ 8 Y8 r4 G" j$ L4 ?
- void test_normal_mul(uint32_t kLoops, float32_t* / c" M2 y6 d- g* j- o
- pSrcA, float32_t* pSrcB, float32_t* 8 A% b+ L% L. j/ S% ?' ]
- pResult, uint32_t lenVector)
8 k9 Q" l& u* Y a - { 3 K- b. n( N: y
- for (uint32_t j = 0; j < kLoops; j++)
$ {( C1 G& A7 Z. D: H- U - { " h! T# e- o6 M7 O3 o; ~, v- `
- pA = pSrcA;
% T6 q) z$ g) Q. ]. k0 v% r# ? - pB = pSrcB;
% F1 o$ q: v3 Q8 c m3 h$ k - pR = pResult;
' }4 s$ X. n+ Y1 M) a& s - ( }% F( d/ p" }; p1 A! ?" b8 b
- for (uint32_t i = 0; i < lenVector; i++) " C! s4 A+ l: _% {) Y* `
- { *pR++ = (*pA++) * (*pB++) ;
- V& M" {/ I7 A: Z9 ^ - }
1 c- l7 w) x; @, c5 T - } - p; C& a/ e8 q5 D0 K
- }
' j9 L' i2 N5 K5 H& x - 6 a: J& V$ b5 C8 D6 _
- #if defined (__FPU_USED) && (__FPU_USED == 1U) ; }: u2 @' |( P7 t: U
- /* Use arm dsp lib to test basic operation Multiply, FPU enabled */
* _1 y7 J) ^% X: |, n7 I - void test_arm_math_mul(uint32_t kLoops, float32_t* pSrcA, float32_t* pSrcB, float32_t*
3 [( s/ a/ C" e: x- I - pResult, uint32_t lenVector)
! h( q" h# p `( {1 E - {
, J5 n6 k X; m - for (uint32_t j = 0; j < kLoops; j++)
% B' u& V5 q/ n6 U - { : s5 f- _- w/ L; c( ]
- pA = pSrcA; //Code alignment with the function without FPU
4 q3 n3 N8 g0 M9 C - pB = pSrcB; ( S8 k9 p: y5 ?! e* u& g
- pR = pResult;& g- ?- i' O& s1 P
- arm_mult_f32(pA, pB, pR, lenVector); 8 J( \% x5 h) {+ S
- }
% h: V$ i+ K$ J7 c2 j! q' y, m/ } - }
) e/ j. `, p5 H/ u- f# R' ~ - #endif / @4 Q+ g% X2 H6 q
9 g0 R9 |' @- C+ \8 i- /**
* M' k6 `5 U0 F - * @brief The application entry point.
: v' t* @* q) \; n - * @retval int
1 B8 Y! b, \$ Y+ ~' q6 M - */ 5 g- h) |7 H/ ~4 H
- int main(void) . r! X. C/ G& x% M) d
- { % _; K2 ?6 \- _. C9 T( K
- /* MCU Configuration------------------------------------------------*/
4 q+ i+ M7 E1 U& [2 @4 U- C. r6 v
0 H2 g1 U Q& C1 I! M( @- /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
3 N8 t$ @! Y% i6 z$ w2 V - HAL_Init();
: _% n. M' D; I7 |1 p Y7 Y - ! |6 \) @2 N" k; c/ o
- /* Configure the system clock */ ; @& U& w, F& ?2 _4 p+ M% S
- SystemClock_Config();
0 n* m2 K8 s& g
4 {( e; D; e' G. L: K5 d. v- …
0 ^# X# L2 P' E" J- L) c2 b4 M9 W - ! I! B( ?: _ x2 m& ]) S# p o/ T
- HAL_Delay(100); 2 U& G8 s* `. S0 e# W! s6 l! c7 A
- , v, a/ x& b# C$ {0 ^
- /* USER CODE BEGIN 2 */
1 b/ Z. W6 p5 ~0 w) x, e - test_normal_mul(10, testInputA, testInputB, testResult, 1024);
3 \5 r' R8 t1 G4 ` - test_normal_mul(10, testInputA, testInputB, testResult, 1024); ! ?8 [8 e6 W2 ]8 ~9 d' X% W# o
! m8 G6 q, X* Z* h% ~- #if defined (__FPU_USED) && (__FPU_USED == 1U) , m3 }( v; d+ m* x3 v6 M7 ?
- // Multiply calculation with arm dsp lib
/ c! z. r& @ h" f, s - test_arm_math_mul(10, testInputA, testInputB, testResult, 1024);% t1 W% Z# c' W) n. s# P
- test_arm_math_mul(10, testInputA, testInputB, testResult, 1024); / K' `+ s9 u# E
- #endif 5 C* z" }/ {* t8 U) ~0 a% W
1 X' \* M0 E; l) L: y6 Q. Q- /* USER CODE END 2 */ & ]9 ]; U7 y0 Q/ j7 a# L1 w
- ( v' {' _! x1 o5 j9 T
- /* Infinite loop */ / T7 R& \0 i. q+ C# @" Z
- /* USER CODE BEGIN WHILE */ r7 x- l5 r6 _! z, B" u
- while (1) ' _/ j; o, z/ D8 R
- {
% e0 k/ |6 B% R3 H' B& d - /* USER CODE END WHILE */ # k% T/ P- I- i( q1 ~
- * w, z& @+ h$ s
- /* USER CODE BEGIN 3 */
8 u( {/ M) y* H - }
: ^. G4 R% b% v1 H6 p" q - /* USER CODE END 3 */
" { M5 K; m1 o1 c/ q - }
复制代码 2.4. 工程配置
Z$ M q4 ^7 j2 G% f% C. k, E通过 KEIL 工程 Options / Target, Floating Point Hardware, 确定 FPU On/Off。
! E7 b3 {0 o) U9 _# ^2 J( K) D# Q v; t; e
8 N; m! M- g/ t; H! p. j
▲ 图3. KEIL 项目工程 FPU 单精度浮点设置 通过 STM32G474_FPU_TEST.sct 文件配置 Data 存放区域,如下例,将测试数据置 于 SRAM2。. z1 v$ [) H! n# z' p
- , L( H' O# y" y! ~7 R( }
- RW_IRAM1 0x20000000 0x00014000 { ; RW data9 X' i2 W; A1 [, u B
- .ANY (+RW +ZI)& t# C. c& @0 d6 U
- }
6 K0 j: B& |; C - RW_IRAM2 0x20014000 0x00004000 {
+ _3 T7 q& M+ w% D2 _+ `, ~! o7 z - *(.TEST_INPUT_A)
& T- z$ p6 o5 P: U: X8 c% |5 o5 w2 r - *(.TEST_INPUT_B)$ e- A# b5 p( I1 A1 {/ w
- *(.TEST_RESULT_D)
+ M6 W5 J( ]& m; j' v% T - }3 z2 W3 w$ D3 j: I! a v2 Q5 A3 g4 q
- RW_CCM 0x20018000 0x00008000 {
0 s8 G2 ~9 d3 n- I/ d - }
复制代码 完成后,进行编译链接,即可进行 STM32G474 FPU 性能的测试。 Z) g3 f* {+ J& E# b( L
$ d4 n1 H! _4 q$ U" o3 }& Q. }2.5. 编译选项 1 Q ]; h/ l" H
本文中我们使用的是 KEIL IDE,设置使用的是 KEIL Compiler V5。为了获得代码最 大程度上优化,我们使用了-O3 优化选项,与-Otime(Optimize for Time)结合使用。该组合选项意味着会进行更多代码优化,如循环展开,更激进的函数内联和自动函数内联 (-O3 默认使用--autoinline)等,当然副作用是二进制代码大小会有所增加。另外,增 加设置 --loop_optimization_level=2 来控制循环展开的优化等级。(注意:-- loop_optimization_level=2 选项只能与-O3 -Otime 一起使用。)如果您对 FPU 架构比 较熟悉,也可以尝试增加—fpu=fpv4-sp(Cortex-M4F FPU 实现的是 FPv4-SP 浮点运 算扩展)等选项,不过一般使用默认即可。, M# R3 `. u5 K
▲ 图4. KEIL 工程,编译选项设置 03使用 KEIL Trace 工具进行测量
; D$ q. p* p, S/ { t3.1. KEIL 工程设置 , z4 \$ V6 f" ]& P
. X& \) d- A9 z% R( B, [KEIL 工程下,首先选择工程选项设置,在 Debug 选项页中,右上部使用 Debugger 工具栏中选 Settings,如下图 5 和图 6 设置。注意 KEIL Trace 设置的时钟必须要与实际 STM32 使用的系统时钟相一致,如图 6 中,STM32G474 使用了 170MHz 的系统时钟, KEIL Trace 中也要相应设置为 170MHz。1 N. B3 ] J& A' p" }
▲ 图5. KEIL 工程,Debugger 设置入口 ▲ 图6. KEIL 工程,Cortex-M Trace 功能设置 运行KEIL debugger,如下图7所示,将断点设置在要测量的语句前及其后,执行 代码,当Debugger停在断点时,其状态栏中t1指示的即为当前代码的已执行时间。测试代码起止时间差即为代码执行用时。该Trace功能计时是比较准确的。当然如果您希望掌控更多,也可以通过代码来实现,如增加诸如如下代码:
6 H& r) e- e$ U, GnStart = DWT->CYCCNT;
5 _* p! _( s/ }! P~~~需测试执行时间的代码~~~
$ A4 C) M! S" s% ^% fnStop = DWT->CYCCNT;
/ Y0 }" {1 |/ @# j% q/ g然后用(nStop – nStart)/系统时钟,换算成时间即可。(我们这里没有考虑中断,一 般测量前需要禁用中断)+ Y; V8 I9 Z+ ? g# t
▲ 图7. KEIL 工程,Debug 模式下 Trace 程序执行时间 3.2. 测试结果
! Z4 L3 P/ ~$ L* B1 I. W9 |# g) k. Q下表列出了STM32G474 10K次 浮点“乘”用时统计。4 \* ~# C% W! T% a6 F
▲ 表1. STM32G474 10K 次 浮点“乘”用时统计表 10 X 1024次浮点乘
# r4 N J# M& O3 q$ V
2 J, p$ k4 f: I增加--loop_optimization_level=2 编译选项. a0 N) t8 X) D% H+ D! d
- w$ S5 f2 H- S2 Q) H8 j# W. w8 U! q
FPU 核心汇编代码的比较,见图8和图9。
9 U1 r* u9 R+ g( x& s2 u, U▲ 图8. 使用--loop_optimization_level=2 编译选项的常规代码汇编 ▲ 图9. ARM DSP 库 arm_mult_f32 函数汇编
3 ^8 C2 s" I1 c& T E9 m+ ]5 `2 B" X7 c* u
使用loop_optimization_level=2, 常规代码使用KEIL compiler V5编译结果与 arm DSP Lib 的核心汇编基本相同。如果不使用loop_optimization_level=2编译选项, 则可以看到其主要区别在于KEIL Compiler V5 与ARM库对loop的unroll 处理程度不 同。在实际应用时,需要根据应用自身需求判断是否需要使用ARM DSP Lib,基本上 ARM DSP Lib是很高效的。
+ c( \4 u. ^7 `& Z" P& A5 A
6 U0 R+ U$ f0 {* ]* t4 I04小结
' t7 o/ h' `0 F: |本文介绍了使用 STM32G474 FPU 进行浮点运算,从系统的角度、ARM DSP Lib、 编译选项的影响等几个方面探讨如何提升整体性能,并介绍了如何利用 KEIL Trace 工具进 行测量。以供在系统性能方面有需求的客户参考借鉴。
# e& ?8 r# |5 ^0 e( n) K( _* ]+ N7 V1 T) x
7 R4 N- a3 J3 _& O6 O9 U/ G
如果你有其他想要的实战笔记,可评论区留言,管管来跟工程师沟通!# w) i. P1 @2 A% k
W4 b7 Z! |. I- D# C
9 [4 A8 _$ z1 E" Y8 L
8 p- }8 T& l8 f$ y9 F( s6 s
* t5 E6 V* w* T7 i8 d- i2 }6 {& G+ w( {
9 ]- o7 d2 @+ O5 ~) F
0 y% Y( C4 _9 n! D' F: g
1 K8 X5 p6 i3 a* E# k. b" D
: c% S6 J/ J8 A; a# L2 G. ]: s0 E
) y. i( l0 L$ i; w5 F+ t$ d
) e% v) b' @6 O+ c2 y! q& c" y. L l+ K
|