- P6 Q- Y: R: g8 G! q& `# N/ A. W* W# y01引言. A% o7 G% W) ?; k, N( I
客户在使用 STM32G474 时,希望使用 FPU 进行浮点运算,并最大化其性能。本文 从 STM32G474 系统的角度、ARM DSP Lib、编译选项的影响等几个方面探讨如何提升整体性能,并介绍如何使用 KEIL 工具进行测量。
6 s8 `1 _6 G g0 S) u6 |- e# W1 v/ _7 h! Z. F7 l
02STM32G474 FPU 运算性能优化
6 D2 T k2 s& F2.1. STM32G474 系统性能优化 0 g% l6 A* s' u0 S: F& s. l# m8 Y4 J
! I1 ?# F- J7 r T1 b
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 并发访问产生的性能下降。应用则需要根据实 际情况,合理使用内存区域。
* ~ n1 [ v' h9 I▲ 图1. STM32G474 架构
" O( L: ^0 ^5 H' ]- a4 d1 O0 ~5 i" [" }* y
2.2. ARM DSP Lib 的使用 ( q/ x+ W' a3 Z% P& o2 Z
在 ARM DSP 库实现了很多 math 算法,可进行浮点乘加、点积、卷积、FFT、NN 等 多种算法 API,可以使用 ARM DSP 库高效使用 FPU。ARM DSP 代码位置如下:
, t4 t( b9 Q# d: R' p9 x. ]* x/ Z4 ?& K6 U l" E. K3 z# y1 x2 x
2.3. 示例代码 % j5 y; s7 [% |; w0 U( H
下面示例代码中对浮点乘法运算进行了测试。用户可以使用 STM32CubeMX 生成 STM32G474 KEIL 工程,在 main.c 文件中加入如下示例代码:
+ P1 W0 L9 D+ {( n* t- * A9 r3 G2 O+ g
- __attribute__((section (".TEST_INPUT_A"))) float32_t testInputA[1024] =
, C( a9 ?; C- D% `5 W% @ - {, f- S0 s, X& \3 ?# {* r' Y3 d
- 0.623234f, 0.799049f, 0.940890f, -0.992092f, 0.212035f, 0.237882f, -
; V5 s: n9 D0 j5 A - 1.007763f, -0.742045f,
7 U" \5 F% ]' S! k0 M3 r9 g - ~~ 这里数组使用动态生成的float数据,数据量较大,略 [0 N4 D$ G7 Q
- -0.417470f, -0.205806f, -0.174323f, 0.217577f, 1.684295f, 0.119528f,+ | k+ s1 x) J
- 0.650667f, 2.080061f9 F6 |5 F" Q, s
- };8 P$ R; f- O" K; T
- __attribute__((section (".TEST_INPUT_B"))) float32_t testInputB[1024] =
* y. d% k. j% k9 @! F/ G' L( J# R - {
' @6 x7 I6 N7 q - -2.423957f, -0.223831f, 0.058070f, -0.424614f, -0.202918f, -1.513077f, -* r3 B) b6 v/ Y# z+ J" R3 \
- 1.126352f, -0.815002f,
6 ?! o- x8 f2 k
4 H( o; x: ]1 ]. O2 a, S- ~~ 这里数组使用动态生成的float数据,数据量较大,略
% a) }! d7 C W. [; S7 ^% P5 m - -0.447001f, -0.725993f, 0.354045f, -0.506772f, -2.103747f, -0.664684f, 1.450110f, -0.329805f
/ R4 \& S$ Q, y8 G - }; - m* V% Y% B, C2 s: X; Z
4 ^8 D/ K4 u/ e, @/ n- X3 ]) X1 N- __attribute__((section (".TEST_RESULT_D"))) float32_t testResult[1024];
1 |) [* y( v @0 @% C5 @
* [8 z$ O( B3 e3 `3 d% W1 x3 j* ]! r- float32_t* pA;
& x% P9 s- ~% g# X4 Y - float32_t* pB;
: P: X- Z; M- c, Z `; m - float32_t* pR;
% p, \% _1 {) N3 Y4 O5 r9 A - /* Private user code --------------------------------------------------*/ * D( Q) ]) _: h9 Q+ g
- /* USER CODE BEGIN 0 */ 2 ?' R: i( K* K7 P$ n: K$ V! G' `
- void test_normal_mul(uint32_t kLoops, float32_t* ! Z8 q- Z5 v( Q! j
- pSrcA, float32_t* pSrcB, float32_t*
: Y0 G! r; I% c8 t - pResult, uint32_t lenVector)
4 g. i8 k5 O4 f* u# l - {
+ }1 y# G2 T( a9 K - for (uint32_t j = 0; j < kLoops; j++)
0 z- |" u) h: M6 f8 J& O; p+ W - { 5 f8 Z5 W; e4 @' X; s3 H$ a: r
- pA = pSrcA;
- v/ e; x6 \- g% T9 u0 k0 m - pB = pSrcB; : E. h: b4 T$ ~1 A ^: h# H9 |
- pR = pResult; 4 n/ Q; a. h" _8 O
) P# m; Z- H' n- z2 X; i1 ?# L- for (uint32_t i = 0; i < lenVector; i++) 8 N" |" E0 \( c' w
- { *pR++ = (*pA++) * (*pB++) ;
/ o# r, G5 x2 L) R% M' f4 X( Y5 U - }
+ m" |: M5 }) @ - } ' W6 l! X0 E1 k, e x
- } 6 G; X2 ^& \6 u' \2 [
- 8 E; r% X: c" t8 v
- #if defined (__FPU_USED) && (__FPU_USED == 1U) $ C2 a m" ?1 _) ^- \+ U
- /* Use arm dsp lib to test basic operation Multiply, FPU enabled */
# C- ], ]# B: h6 x( X5 x$ K - void test_arm_math_mul(uint32_t kLoops, float32_t* pSrcA, float32_t* pSrcB, float32_t* 2 b, E0 y" J: ^7 { U; A2 U
- pResult, uint32_t lenVector) 7 p: k4 {2 T& q+ b
- { ( A: b/ v) I+ E- D& u1 S
- for (uint32_t j = 0; j < kLoops; j++)
( }3 x: N* _6 ^& }$ r' \ - { ( b0 p, R; Y! a8 `! y8 h( W
- pA = pSrcA; //Code alignment with the function without FPU % ^! y n. ~- h! I: S5 l8 R
- pB = pSrcB; : P. w6 { ]/ c9 j+ x0 F3 m
- pR = pResult;
4 ]( o) A% J( i/ K; V# ~: r - arm_mult_f32(pA, pB, pR, lenVector);
) _ d( Q0 }1 X - } & a4 t- U. c2 U
- }
( ^& v5 c0 ^9 t5 i4 r& D/ V - #endif ' E9 J3 N1 |( T
) F# v$ J3 ?( r! o; ~- /** & R0 O K8 _' \( B0 y
- * @brief The application entry point.
) C' E X% e2 ~% n; W i- _ - * @retval int * r0 \1 e" K; ?, K3 [% F: A
- */
$ l( Z" X) [7 T9 }$ m% @ - int main(void) ' |- @/ V7 X9 v( j" z
- { + d+ Y' `0 n$ Y j( p
- /* MCU Configuration------------------------------------------------*/ ( Q1 N2 e& x/ I, T4 v6 W& S
: a% q' B! Z+ f7 [' o% I- /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
& L. z' i) R0 W6 U V7 _ - HAL_Init();
) X5 x! |/ b4 { - - z9 ]6 C: T' L1 T
- /* Configure the system clock */ 7 E/ T" g4 F: q0 @ e s- I
- SystemClock_Config();
; J+ Y; N- G1 [8 H1 Z0 r) d - + ?) h$ V; r5 O- P- b# R
- …
) r$ k/ {% X4 ]2 N2 G& W2 @
2 E( Y+ T% V. r u- HAL_Delay(100); + E+ _% \8 n+ ^7 ^* ~" v1 L
- $ \) g* y" d$ ~9 n
- /* USER CODE BEGIN 2 */
3 z& T- v6 P t3 i3 f8 T7 W - test_normal_mul(10, testInputA, testInputB, testResult, 1024);. b$ i/ T5 j+ o7 D8 l" a
- test_normal_mul(10, testInputA, testInputB, testResult, 1024); 7 G# }2 p! R- r7 H
9 m' W( h: s. d& j2 N4 x) b: ]- #if defined (__FPU_USED) && (__FPU_USED == 1U)
/ l1 E$ k% T6 L3 M5 ?; _ - // Multiply calculation with arm dsp lib
0 A, J( z+ R: J0 @9 d. H - test_arm_math_mul(10, testInputA, testInputB, testResult, 1024);
2 x' [+ F! L# ]) }/ ? l - test_arm_math_mul(10, testInputA, testInputB, testResult, 1024);
% G0 ]- h0 [7 W$ P - #endif
1 s# Z8 x) o2 E5 Q. G5 {
5 v0 g9 q1 u6 `7 j- /* USER CODE END 2 */ : K6 D$ ^( I3 Y- o' D) k9 ~! I
4 @) y6 R2 N* q' E7 {- /* Infinite loop */
* \: W8 m4 R) P4 k& Z - /* USER CODE BEGIN WHILE */
- ~+ m6 O, j/ u* V1 ^ - while (1) & c8 y: L, ~3 t
- {
3 H0 b9 F7 |1 t1 ]9 F6 E - /* USER CODE END WHILE */ 0 i7 j7 J9 b& H+ a3 h3 d" B
- + C; [. F: ]0 N8 Z( A- x/ n
- /* USER CODE BEGIN 3 */
4 x) ~6 @( A( i+ Q6 l$ G - }
4 _7 ?& r9 {, O h0 M5 N - /* USER CODE END 3 */
1 H8 M2 j# k0 t6 ^ - }
复制代码 2.4. 工程配置
& z* d7 p9 I/ B$ X+ M+ M$ S8 [通过 KEIL 工程 Options / Target, Floating Point Hardware, 确定 FPU On/Off。
) `' H4 p/ z4 s1 a; w/ Q! z) ]% f/ b: l6 D# }% v3 w# {0 ] |1 R
0 U( Y2 n* j$ M2 N" {! R+ I3 Q▲ 图3. KEIL 项目工程 FPU 单精度浮点设置 通过 STM32G474_FPU_TEST.sct 文件配置 Data 存放区域,如下例,将测试数据置 于 SRAM2。+ H/ K0 P+ N. i# \9 o4 e
; Q# f D' X4 l" ], T- RW_IRAM1 0x20000000 0x00014000 { ; RW data
1 [2 h( @, N: |9 j+ e9 S6 j - .ANY (+RW +ZI)7 v8 J I5 |3 y5 `. ?
- }; K2 h) v! D+ d6 U. V
- RW_IRAM2 0x20014000 0x00004000 {
- i/ `) @: l6 A w: o6 k - *(.TEST_INPUT_A)8 r0 P$ X$ K) D7 s7 u
- *(.TEST_INPUT_B)( t1 ~; f( X3 j2 S
- *(.TEST_RESULT_D)
) l2 E% f# c1 p3 L. c - }
* ?* B& ~' Y. H4 y# x% v! F - RW_CCM 0x20018000 0x00008000 {$ B1 Q9 {6 X" E" a9 l
- }
复制代码 完成后,进行编译链接,即可进行 STM32G474 FPU 性能的测试。
E* Z+ F( f9 A- }
) T' S* U6 g3 X! g5 r2.5. 编译选项
( Q$ l# h6 P; `8 e$ B8 ^本文中我们使用的是 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+ _9 n* q1 K
▲ 图4. KEIL 工程,编译选项设置 03使用 KEIL Trace 工具进行测量
& d, M1 s4 G% h: {3.1. KEIL 工程设置
: Z$ U l; X. v
3 j, V6 D. \6 E5 d4 W1 NKEIL 工程下,首先选择工程选项设置,在 Debug 选项页中,右上部使用 Debugger 工具栏中选 Settings,如下图 5 和图 6 设置。注意 KEIL Trace 设置的时钟必须要与实际 STM32 使用的系统时钟相一致,如图 6 中,STM32G474 使用了 170MHz 的系统时钟, KEIL Trace 中也要相应设置为 170MHz。
3 J* w4 c& G5 M, u▲ 图5. KEIL 工程,Debugger 设置入口 ▲ 图6. KEIL 工程,Cortex-M Trace 功能设置 运行KEIL debugger,如下图7所示,将断点设置在要测量的语句前及其后,执行 代码,当Debugger停在断点时,其状态栏中t1指示的即为当前代码的已执行时间。测试代码起止时间差即为代码执行用时。该Trace功能计时是比较准确的。当然如果您希望掌控更多,也可以通过代码来实现,如增加诸如如下代码:/ n2 [0 a( l7 G
nStart = DWT->CYCCNT; ' d/ s l' Y# f* a
~~~需测试执行时间的代码~~~ $ u" @9 M* [2 O
nStop = DWT->CYCCNT;
$ u. M( i. f( n然后用(nStop – nStart)/系统时钟,换算成时间即可。(我们这里没有考虑中断,一 般测量前需要禁用中断)
1 @* A. }' t9 l5 o4 L0 e▲ 图7. KEIL 工程,Debug 模式下 Trace 程序执行时间 3.2. 测试结果
# u/ T3 f. C O" m1 j: y% ?) k下表列出了STM32G474 10K次 浮点“乘”用时统计。4 m& J a9 o/ p0 E# S: \# U2 E
▲ 表1. STM32G474 10K 次 浮点“乘”用时统计表 10 X 1024次浮点乘4 m6 e" a' z' f$ E7 m( P
( F( q# a1 h5 W+ O增加--loop_optimization_level=2 编译选项! M7 F: \# s3 I& @" j U5 C6 _+ L
- @9 N6 i: Y0 H; E0 t2 h: E) O, w( J% e$ BFPU 核心汇编代码的比较,见图8和图9。5 p* e& m2 d) s* j. t8 z" e0 e3 i6 C
▲ 图8. 使用--loop_optimization_level=2 编译选项的常规代码汇编 ▲ 图9. ARM DSP 库 arm_mult_f32 函数汇编 ; |5 p* X" B9 x6 O: y8 c5 T
/ |8 d9 M- |, o0 ^使用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是很高效的。
; X4 \2 A2 l( W( S4 ]& n. ~: e: v3 b/ C7 h8 A
04小结
( Q% Y* Z* j) ]1 _5 M# s- Y本文介绍了使用 STM32G474 FPU 进行浮点运算,从系统的角度、ARM DSP Lib、 编译选项的影响等几个方面探讨如何提升整体性能,并介绍了如何利用 KEIL Trace 工具进 行测量。以供在系统性能方面有需求的客户参考借鉴。0 N6 i& F0 m% _, c$ O0 C) t0 x
. c& k- ?& b) L/ t: d' _

9 K6 M/ J- S' c6 T8 J- j: S如果你有其他想要的实战笔记,可评论区留言,管管来跟工程师沟通!
% {% L- B: s9 s$ E! \( N0 l# L5 t- Y7 P
; n0 ^5 }. E% T* G- b) ]& r
0 n: q6 {& W, d% q& f5 d* L0 Z7 z( j% Q$ m' Q1 J1 t% V: S* Y- L8 |. C
8 R. a3 k5 S8 W" I: s5 V, B9 ~, s1 h5 F1 J* Q6 `$ @# N/ N q
2 k# K" N5 ~+ A$ c# y. _1 o! h9 Q) [2 C. N! O1 z
2 }, @5 c R+ ]" Y& h# b; Z
* Q0 X/ g" Q i. O% q8 o
( J7 R! i0 p9 U" |+ p0 q" b" ~
1 ~3 J" z0 g+ L' Y
! M& N+ z D! d. X; L/ ] |