. S5 z0 T: X: A01引言9 {% s/ T/ ?1 }
客户在使用 STM32G474 时,希望使用 FPU 进行浮点运算,并最大化其性能。本文 从 STM32G474 系统的角度、ARM DSP Lib、编译选项的影响等几个方面探讨如何提升整体性能,并介绍如何使用 KEIL 工具进行测量。; u( N6 x. u8 g y( ]
1 |, A, G) e+ g
02STM32G474 FPU 运算性能优化 ! b4 m! T8 f6 S7 K i; d
2.1. STM32G474 系统性能优化 6 n( b( x) u: O( ~
# o. h" P$ L6 }0 S- q& F. Z! F+ @STM32G474 使用的是 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 并发访问产生的性能下降。应用则需要根据实 际情况,合理使用内存区域。/ u9 Q6 n, g0 M* X& q' R
▲ 图1. STM32G474 架构 9 ]: _3 Q; z: c
3 P C- A9 ~/ }/ Q
2.2. ARM DSP Lib 的使用
# o# W0 y( _# I( L在 ARM DSP 库实现了很多 math 算法,可进行浮点乘加、点积、卷积、FFT、NN 等 多种算法 API,可以使用 ARM DSP 库高效使用 FPU。ARM DSP 代码位置如下: s6 |5 x; @: h* z t. _. Q
+ k) G4 |: d' W
2.3. 示例代码 ( }0 t/ l4 D5 ^9 U( c4 Y) ?
下面示例代码中对浮点乘法运算进行了测试。用户可以使用 STM32CubeMX 生成 STM32G474 KEIL 工程,在 main.c 文件中加入如下示例代码:
! c. {, G* L" i( |3 }% o. n( r
/ ^% e" Q, q" u/ F7 s. m- __attribute__((section (".TEST_INPUT_A"))) float32_t testInputA[1024] = 3 l0 N0 a# Y, C2 |- X1 I( x
- {) F# u/ ~, B1 S b4 Z7 X X" E
- 0.623234f, 0.799049f, 0.940890f, -0.992092f, 0.212035f, 0.237882f, -8 ]1 N: L* ^4 w* d+ D) j+ d3 N
- 1.007763f, -0.742045f,
8 c, [7 N! J/ b - ~~ 这里数组使用动态生成的float数据,数据量较大,略
2 }, R4 c% Z3 U4 v" a8 S3 v - -0.417470f, -0.205806f, -0.174323f, 0.217577f, 1.684295f, 0.119528f,
; N, t* M" q2 x* G' p! | - 0.650667f, 2.080061f
, L& }( T, R6 h( l; a - };! s6 A/ g4 |: z2 ~( |/ U3 t+ h
- __attribute__((section (".TEST_INPUT_B"))) float32_t testInputB[1024] =
/ ]9 T! ?+ g1 M& \2 }% a( u6 ?3 n - {4 ?8 t' K3 w9 _- ?# u. ~
- -2.423957f, -0.223831f, 0.058070f, -0.424614f, -0.202918f, -1.513077f, -/ p9 [3 V6 _" ^( [
- 1.126352f, -0.815002f,
: w5 k2 d- l# D' b- X: l6 G/ b+ P9 D - / P1 g, O7 J V/ ?/ v( G/ ~9 S( t
- ~~ 这里数组使用动态生成的float数据,数据量较大,略
8 j3 W& M% o9 E$ V% Q( m - -0.447001f, -0.725993f, 0.354045f, -0.506772f, -2.103747f, -0.664684f, 1.450110f, -0.329805f
/ l0 h& C9 K( t0 t5 e - };
: s/ ?5 {. A+ f/ H3 r8 ~* X
! O# s* B7 r" m3 B- __attribute__((section (".TEST_RESULT_D"))) float32_t testResult[1024]; - H, M/ D/ }7 u2 ], T) A
/ A) F1 C. K0 W- float32_t* pA;
7 u5 w( M( l3 A" P - float32_t* pB;
+ [6 g! B0 s# z0 x7 c$ K6 j" [/ K) m' A - float32_t* pR;
c- d' H& r& B/ |( O - /* Private user code --------------------------------------------------*/ 6 p( _" G- B; H
- /* USER CODE BEGIN 0 */ % q. T; S o& e) H
- void test_normal_mul(uint32_t kLoops, float32_t* ( m1 F2 N% v: |$ u" v# E2 L! `
- pSrcA, float32_t* pSrcB, float32_t* 1 E! H g2 o4 U/ v
- pResult, uint32_t lenVector)
, f0 {# G- l' w) U" E, F: k2 J6 n k - { , N& U- C7 v }% H) ^
- for (uint32_t j = 0; j < kLoops; j++)
& z+ N Y5 q* A+ J& x# N' x - { : {( ]" ]# }8 s1 ^
- pA = pSrcA;
0 g( y) [: ?( ]( ^ - pB = pSrcB; * B! w! }! r( p+ e) t
- pR = pResult; 9 f" ~! `) l# F, [1 o0 X/ h- F) G4 u
: i# s# p; |( P1 I8 _/ R- for (uint32_t i = 0; i < lenVector; i++) Z! t* K; v( @9 v
- { *pR++ = (*pA++) * (*pB++) ;
! |/ c+ z0 b% s3 l/ M - }
, A0 t9 T4 X" t" w- ~# l J - }
6 R1 a, c3 B- _( L - }
! p$ l0 E5 b# \. a1 I/ d - 4 H; f/ G: @+ l
- #if defined (__FPU_USED) && (__FPU_USED == 1U)
9 A% c; o0 X8 ?/ {$ I, K9 A* T - /* Use arm dsp lib to test basic operation Multiply, FPU enabled */
' V, b4 B. Q, V2 h' D/ M* E8 E: G2 c2 F - void test_arm_math_mul(uint32_t kLoops, float32_t* pSrcA, float32_t* pSrcB, float32_t* & v4 i9 L6 w' n
- pResult, uint32_t lenVector) 9 ~' J$ A# d3 b9 J
- { ! v8 d# ]7 P; i6 D/ E
- for (uint32_t j = 0; j < kLoops; j++)
: Y- E/ g9 |. j' h - {
0 w6 U: V! `- a1 g8 c) X8 W - pA = pSrcA; //Code alignment with the function without FPU ; c0 o8 j7 D2 N9 w- @! d" x) R
- pB = pSrcB;
8 W$ Z, ?7 z$ E1 I- p$ e - pR = pResult;9 q" b9 @% K8 {2 Y; q0 g/ G h
- arm_mult_f32(pA, pB, pR, lenVector); 0 l# V& z3 G7 v6 V
- } ' H6 W$ }+ D* G$ k. U& n2 K
- }
# |# a( t2 L! z( T% o/ O$ u - #endif 4 Y6 k1 f7 o: r
* [* x N+ U% p' b" E' L- /**
) u ^* C8 ?! o& { - * @brief The application entry point.
' t y5 d( P% g+ t9 x - * @retval int
5 y( ]3 h, X: ^ r# H - */
' z/ r& s% c& ] M8 C3 l9 x - int main(void) $ A; |5 {/ M! j* l+ ?
- { 7 x" v: z% j5 e, u3 g: |: Y3 x
- /* MCU Configuration------------------------------------------------*/
2 V% |" k6 S# g+ w7 r9 ?. g, @: H
, b1 q @- V! J# ]& H4 K- /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
8 C% a; A" S; i6 s7 S; B$ q - HAL_Init(); 1 ~& b' w3 M3 Z0 M m; I
8 M- K& g+ }1 J0 J. c1 y& V: {9 R- /* Configure the system clock */ * G1 N( G2 V$ Y% V6 ^7 ~ ~. R- C
- SystemClock_Config();
7 F9 E, [& N/ c7 [2 b3 a7 j
9 N6 N+ U# a: I0 ?1 a- …
( v) N* ^( [" w4 L" n
3 q- f$ {/ H) \- V4 I0 A- HAL_Delay(100); 9 x) [8 _0 ]$ V/ _2 X
- 2 h+ N+ C E3 w9 i2 |3 Q
- /* USER CODE BEGIN 2 */
H% ~$ y- r) h7 f( H x- H - test_normal_mul(10, testInputA, testInputB, testResult, 1024);
7 } q0 q/ S ]$ a% e4 t/ U - test_normal_mul(10, testInputA, testInputB, testResult, 1024);
Y0 ` c" D' E+ V - % b* K7 D) {' {
- #if defined (__FPU_USED) && (__FPU_USED == 1U) " }* L' X2 d3 H9 o, j( H8 a
- // Multiply calculation with arm dsp lib
$ j% g5 i$ N7 V% _1 B/ ^' n8 A - test_arm_math_mul(10, testInputA, testInputB, testResult, 1024);7 z9 @5 @! u2 R& v" K& [
- test_arm_math_mul(10, testInputA, testInputB, testResult, 1024); 9 z3 }0 t0 }& z. K8 {) ]5 K6 Z
- #endif
; I" @$ S0 \6 o6 I' k' T! y8 ~
9 x* D, V- `% n2 t- /* USER CODE END 2 */
+ P+ K& }( J5 v$ E - ( r- J: ~: R% c7 L3 u7 M
- /* Infinite loop */ 0 e6 t6 D% B' \+ v" [# ^4 c
- /* USER CODE BEGIN WHILE */
1 N4 A' q/ K+ E4 D - while (1)
" I9 j* g D- m - {. f' C- @7 Z: `0 N3 R9 R6 D
- /* USER CODE END WHILE */
5 t6 }$ Z+ b1 U5 q( ?: s3 w' T- ^# U% W
3 I! R1 G) m' W0 ]2 e7 M- /* USER CODE BEGIN 3 */
/ Q' V0 L! o6 |! r- P, j - } 7 T) f J6 y* Z: A, r5 R' L
- /* USER CODE END 3 */
# R) {5 P$ v* C8 e - }
复制代码 2.4. 工程配置 : ]; b% u/ ~5 P" y% G
通过 KEIL 工程 Options / Target, Floating Point Hardware, 确定 FPU On/Off。
Z5 t* @( \ a7 g5 } a# T \6 p2 G4 r
4 B, ]. ?+ w- @8 B▲ 图3. KEIL 项目工程 FPU 单精度浮点设置 通过 STM32G474_FPU_TEST.sct 文件配置 Data 存放区域,如下例,将测试数据置 于 SRAM2。
! Y; V, [9 Z7 {- 0 [/ C$ A: k2 ?( R7 {* w
- RW_IRAM1 0x20000000 0x00014000 { ; RW data
7 C& W1 {0 z/ c - .ANY (+RW +ZI)
$ B8 K" ?" ~% H( N1 G# N n - }
9 g5 x, a: Q, }0 {- r( @% q! f - RW_IRAM2 0x20014000 0x00004000 {* h( O1 I, ~* E4 ~+ j
- *(.TEST_INPUT_A)7 c, Y8 v3 \) [: H" S k
- *(.TEST_INPUT_B)" {9 E/ j6 E4 x4 E8 Y p6 H' e9 Z
- *(.TEST_RESULT_D), l# A' r7 a* b+ ^
- }- x- @, `) z/ }" h2 X, c0 ]8 d
- RW_CCM 0x20018000 0x00008000 {
1 d8 w9 K+ R, P8 |" G3 O3 ^ e - }
复制代码 完成后,进行编译链接,即可进行 STM32G474 FPU 性能的测试。3 Y) j! D) [4 ^0 v
1 T1 k# x3 {9 u, q2 S
2.5. 编译选项 5 G( m7 p7 `5 \+ o% |" X
本文中我们使用的是 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 浮点运 算扩展)等选项,不过一般使用默认即可。
( C w7 p" o1 z' Q2 S▲ 图4. KEIL 工程,编译选项设置 03使用 KEIL Trace 工具进行测量
8 O Z8 r# c& v) G3.1. KEIL 工程设置
, R6 ?5 X0 w) w/ s: N4 S
+ D* }/ W- X- [" `: I, H) p0 L8 bKEIL 工程下,首先选择工程选项设置,在 Debug 选项页中,右上部使用 Debugger 工具栏中选 Settings,如下图 5 和图 6 设置。注意 KEIL Trace 设置的时钟必须要与实际 STM32 使用的系统时钟相一致,如图 6 中,STM32G474 使用了 170MHz 的系统时钟, KEIL Trace 中也要相应设置为 170MHz。
: u& e' I* V2 W# r. N▲ 图5. KEIL 工程,Debugger 设置入口 ▲ 图6. KEIL 工程,Cortex-M Trace 功能设置 运行KEIL debugger,如下图7所示,将断点设置在要测量的语句前及其后,执行 代码,当Debugger停在断点时,其状态栏中t1指示的即为当前代码的已执行时间。测试代码起止时间差即为代码执行用时。该Trace功能计时是比较准确的。当然如果您希望掌控更多,也可以通过代码来实现,如增加诸如如下代码:$ [- z+ C# W0 P- F
nStart = DWT->CYCCNT; 0 v2 o7 q! w6 B, O* ?. U
~~~需测试执行时间的代码~~~ ( n+ G6 v+ Q2 Q x
nStop = DWT->CYCCNT;
+ C; z6 V N% X$ \/ q然后用(nStop – nStart)/系统时钟,换算成时间即可。(我们这里没有考虑中断,一 般测量前需要禁用中断)
/ F( r' g4 n, g* X5 t# z▲ 图7. KEIL 工程,Debug 模式下 Trace 程序执行时间 3.2. 测试结果: K* `7 R. c) a$ X8 {: L3 b
下表列出了STM32G474 10K次 浮点“乘”用时统计。
1 G5 \& ^" l/ @1 l* l8 ~▲ 表1. STM32G474 10K 次 浮点“乘”用时统计表 10 X 1024次浮点乘
( X; e- h3 I5 p% f' u* c# g
4 }. l b2 @3 f$ V0 Z增加--loop_optimization_level=2 编译选项8 u. C: Z: ^: T! r9 C$ | e' z0 j
' [% U# ~) }+ e4 d+ O. D& DFPU 核心汇编代码的比较,见图8和图9。
8 P) }7 W W1 W L* C+ j▲ 图8. 使用--loop_optimization_level=2 编译选项的常规代码汇编 ▲ 图9. ARM DSP 库 arm_mult_f32 函数汇编
; e. e$ ]" U" A! V1 u9 Q7 m: m' r: r4 R1 n
使用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是很高效的。. I1 ]4 {5 g; X4 I$ g x
: ^7 G7 U0 Q0 f0 x+ j, V04小结 0 w8 X i/ F q% f- B) c* b
本文介绍了使用 STM32G474 FPU 进行浮点运算,从系统的角度、ARM DSP Lib、 编译选项的影响等几个方面探讨如何提升整体性能,并介绍了如何利用 KEIL Trace 工具进 行测量。以供在系统性能方面有需求的客户参考借鉴。* _8 S6 f+ n6 L& b9 N2 p9 N
) N/ S% E. w {, v0 K8 n" ?

* g3 A3 q4 J% r* }# A! Y- b! g8 c6 r如果你有其他想要的实战笔记,可评论区留言,管管来跟工程师沟通!1 D# y6 _" P5 |
# }) U p6 u; |# C! X: e* ^
' B8 G# e) B! ^/ U/ O! q D6 m; }
5 G6 Z# G, w% w0 f
6 Q. J$ }5 R" X- g$ I8 p( \8 q! \! M5 Z$ t+ m' v5 n0 r
$ ?/ Y/ y' n( i# E: {/ R7 I
7 w% G+ m, j# h; a3 b6 u
$ I( C3 y& J9 {* X8 P- Q( O$ r0 m9 [! `. {
' n- j1 r; \9 O; O/ ~/ O% D2 W
" u2 Z0 r1 s* Z* V8 z$ H* m* W0 L6 `
( E, }8 c0 H0 F5 p6 u
|