5 o' P5 b, T! V7 L* F01引言) Y" b) F8 Q% C8 A1 S3 k' v4 s
客户在使用 STM32G474 时,希望使用 FPU 进行浮点运算,并最大化其性能。本文 从 STM32G474 系统的角度、ARM DSP Lib、编译选项的影响等几个方面探讨如何提升整体性能,并介绍如何使用 KEIL 工具进行测量。8 z9 T0 b0 m! Y v
0 x) N2 j5 \! ]
02STM32G474 FPU 运算性能优化
6 E, R% w2 l4 @$ Z( T2 }% B* J2.1. STM32G474 系统性能优化 9 P' t, H" I# q8 [" V1 e
. d# b* X' |4 P( b7 b( M
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 并发访问产生的性能下降。应用则需要根据实 际情况,合理使用内存区域。
! }7 l5 J% K5 Q" h4 \4 c▲ 图1. STM32G474 架构 / Q* T- ^' [1 Q' ]& S4 ^; q$ p
! S" M) ~2 i2 v, ~/ |# ]6 k2.2. ARM DSP Lib 的使用 / g$ ~0 S$ ]+ k6 d" I0 T1 v+ [
在 ARM DSP 库实现了很多 math 算法,可进行浮点乘加、点积、卷积、FFT、NN 等 多种算法 API,可以使用 ARM DSP 库高效使用 FPU。ARM DSP 代码位置如下: x2 G/ c2 e3 S; ~
. q, J& r7 M. [' `" u1 I. V$ Q2.3. 示例代码
: w: n$ y# i2 r3 j# i下面示例代码中对浮点乘法运算进行了测试。用户可以使用 STM32CubeMX 生成 STM32G474 KEIL 工程,在 main.c 文件中加入如下示例代码:% k# M- u+ `- C( T g
0 R1 W- M& t8 g/ i- __attribute__((section (".TEST_INPUT_A"))) float32_t testInputA[1024] =
' r6 R0 v8 M* a2 t! K% { - {
0 c( I4 t, N+ }( \+ b* D0 G; W - 0.623234f, 0.799049f, 0.940890f, -0.992092f, 0.212035f, 0.237882f, -
- d; k6 k% p' w# W6 \: Z" s - 1.007763f, -0.742045f,
2 S# H4 Y5 z6 X6 T# P/ V - ~~ 这里数组使用动态生成的float数据,数据量较大,略8 L& d( X, F, |2 ]
- -0.417470f, -0.205806f, -0.174323f, 0.217577f, 1.684295f, 0.119528f,+ y: i* @& x2 @) {
- 0.650667f, 2.080061f
9 Z/ B" Q4 ], d* a+ ]+ ]& Q! y1 a - };8 W- J1 g2 M+ d: H# I
- __attribute__((section (".TEST_INPUT_B"))) float32_t testInputB[1024] =
& _* ?* g0 h+ ~' S3 }4 W' t - {
: K* _ k- |! F5 Z s" { - -2.423957f, -0.223831f, 0.058070f, -0.424614f, -0.202918f, -1.513077f, -
6 N4 y) _- u& V8 L) w2 i - 1.126352f, -0.815002f,9 F N& U0 t, n( l, @$ n" A+ U" g
- 2 v! W5 o* P4 B8 o6 k
- ~~ 这里数组使用动态生成的float数据,数据量较大,略 1 m% S6 l# S1 {7 Q0 w9 T v- z
- -0.447001f, -0.725993f, 0.354045f, -0.506772f, -2.103747f, -0.664684f, 1.450110f, -0.329805f 0 G8 |: a0 X4 P, \4 h L$ T
- };
3 J L, w4 O- U* @0 N' v& e
" R/ T l- U$ ?9 b/ d: e* Q( R9 b7 b% a- __attribute__((section (".TEST_RESULT_D"))) float32_t testResult[1024]; * @/ r4 ~. ?0 }) H3 T6 i4 H
- $ A" G9 M6 ^! E4 h d% f' m
- float32_t* pA;# m! L/ N: D3 n" x
- float32_t* pB;
% j+ B$ f6 M; b, U - float32_t* pR;
6 G3 ~6 f5 h4 u3 q; y - /* Private user code --------------------------------------------------*/
' |9 F) X$ q, A, ] - /* USER CODE BEGIN 0 */ . g( x2 C4 E) x. `, @& J) `% D
- void test_normal_mul(uint32_t kLoops, float32_t* 4 A0 j7 Z( c5 C5 d3 O
- pSrcA, float32_t* pSrcB, float32_t*
! J5 r3 \( H+ l - pResult, uint32_t lenVector)
2 x; Y% C) Y( H2 W - { c; |4 D' z7 [7 I
- for (uint32_t j = 0; j < kLoops; j++)
- y7 Z& R0 S ` - {
3 F& q6 S' _, N# R - pA = pSrcA; * l7 c$ j: y+ f* x- r
- pB = pSrcB; % K4 {. [( ]5 d7 J' ?. d6 J
- pR = pResult;
: u$ w8 Y* f0 t7 [1 |' j1 q- `
* O5 ]) r6 L: X q7 ^9 H+ Y- for (uint32_t i = 0; i < lenVector; i++)
, U$ B3 g$ W0 j. H5 b - { *pR++ = (*pA++) * (*pB++) ; # e& W5 h: T- K) T
- } 9 \. G. r+ n0 w( O0 ]
- }
2 V. q+ U" }' N) C/ u. q9 T - } ; {1 g Y5 H2 u( j9 ?$ d" r) O
- : ^4 Q1 U& G+ t* R2 A1 m
- #if defined (__FPU_USED) && (__FPU_USED == 1U)
$ Q' ?0 y7 U, L2 x2 f& _ - /* Use arm dsp lib to test basic operation Multiply, FPU enabled */
1 P. K% u# A& T5 @ - void test_arm_math_mul(uint32_t kLoops, float32_t* pSrcA, float32_t* pSrcB, float32_t* - I; t' t* T$ @+ Y M$ i
- pResult, uint32_t lenVector)
; \1 X; O( ]9 Y1 W - { 4 ]) b/ m+ w0 u: C) x
- for (uint32_t j = 0; j < kLoops; j++) ' \2 q& c3 x9 @) X7 _
- {
" V; ]) ]6 E3 y4 I9 g: g - pA = pSrcA; //Code alignment with the function without FPU 5 V! j* I( @/ X/ ~+ ?' n) z
- pB = pSrcB;
. I; t( Z2 g" ?; l! R, U6 } - pR = pResult;3 ~( L" |6 C0 @5 P' d
- arm_mult_f32(pA, pB, pR, lenVector);
3 ]" u6 v# G$ m- T! w6 I9 m - }
0 @% F& M" v3 \, M( o4 t. O - } 7 A2 x0 r0 l( H- ]
- #endif 9 x$ p ?4 \% N, G/ a1 K7 g
- ) A! A @8 |2 e q* V% ?+ M- {) m
- /**
5 h% P& B, k# u: T8 T. Z* r - * @brief The application entry point. " C. W. J6 b- q* j9 R( d) n
- * @retval int
4 y2 L- o6 o4 {" Y( M: q - */
3 X9 ^6 p: p- D3 S2 k! M0 [1 k - int main(void)
) u! n7 b# P% o* F. B1 U - { 3 K9 n/ T+ y! Y: [
- /* MCU Configuration------------------------------------------------*/
- ?9 i2 }* x# |0 g$ ]( ] - 3 `8 S2 K: N8 C4 b! k6 @6 \) s
- /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
' x& |& p# y; T& _5 r' }5 Y - HAL_Init(); - Q3 |4 U3 |$ R2 c" R0 R
- # ^: d7 |# O9 {. p6 g+ J% F" W5 |
- /* Configure the system clock */
+ t7 V. X& K% M k# Z - SystemClock_Config();
3 ~: I8 f6 a8 T5 i/ k
6 s; u" y" d% V5 |- …
1 L8 K1 n) f/ b( c. B5 t' E+ W - . \1 Q: i1 o1 V
- HAL_Delay(100); 5 {8 T0 b# n9 e* W8 Z! |' O0 b; e
- 3 z: h! s/ A# c( s0 w
- /* USER CODE BEGIN 2 */
5 }7 L9 l9 Z" \1 m& u5 p- d7 ~ - test_normal_mul(10, testInputA, testInputB, testResult, 1024);7 g" r5 B3 d! _ L1 [
- test_normal_mul(10, testInputA, testInputB, testResult, 1024);
( {9 q/ c4 F4 N7 A8 ] - . C8 U$ S s4 H+ j/ {' o- O) S) Q
- #if defined (__FPU_USED) && (__FPU_USED == 1U) ! l9 E" q& R8 z' E! t
- // Multiply calculation with arm dsp lib ' y8 U4 P: B3 h6 G6 _" X/ S
- test_arm_math_mul(10, testInputA, testInputB, testResult, 1024);3 j' k: Y" b& t$ Z4 e
- test_arm_math_mul(10, testInputA, testInputB, testResult, 1024);
- K1 n/ E+ y+ T: M1 K" H/ g - #endif
8 K! g; x! h# g4 i1 J
1 k2 I; W! b+ n, G0 [% p5 s! g- /* USER CODE END 2 */
z& N/ N: c% a1 M3 v0 D - 4 e6 D. Q H+ C# I* _
- /* Infinite loop */ ' H8 Y8 Y4 C$ |
- /* USER CODE BEGIN WHILE */ 5 c* G3 F' I( y; x! K
- while (1) ! k2 Z- U1 x6 _) L# d
- {
7 _1 y: L" B# h - /* USER CODE END WHILE */ 5 x; L# G0 Y" s+ F& Z$ X
6 l* y/ ~7 L( {- S1 t) Q- /* USER CODE BEGIN 3 */ ) \6 e% h9 J. U3 t, S
- }
# x; i7 A( B. w) G t - /* USER CODE END 3 */ / D( R8 u% R3 n0 z! [
- }
复制代码 2.4. 工程配置 2 P; O8 Z ]; i+ n
通过 KEIL 工程 Options / Target, Floating Point Hardware, 确定 FPU On/Off。
( C- ]' ^* x, h7 q0 |
) x% @* v' n# f' p( @) j' h
! f I7 x) f7 {* Z: w▲ 图3. KEIL 项目工程 FPU 单精度浮点设置 通过 STM32G474_FPU_TEST.sct 文件配置 Data 存放区域,如下例,将测试数据置 于 SRAM2。
. ^2 B7 U3 Z: L. W
2 e- ]* I* G- M- j" {- RW_IRAM1 0x20000000 0x00014000 { ; RW data
2 e# D, Y! l/ y* X* a - .ANY (+RW +ZI)
! T" }; B1 k! ]$ h! \ - }
- A; R5 d" u/ o$ F3 Q; M; ? - RW_IRAM2 0x20014000 0x00004000 {+ N: b! J7 w& j
- *(.TEST_INPUT_A)$ p0 {. W4 E; P
- *(.TEST_INPUT_B)1 C! R- s- @6 d! N4 E6 y
- *(.TEST_RESULT_D)
* b, I6 h9 j, j* T" f& W - }/ F% f$ I. I8 k" w v: M5 p* A
- RW_CCM 0x20018000 0x00008000 {7 j7 C( y) I3 M4 ~" z, h' F+ R% K! Z
- }
复制代码 完成后,进行编译链接,即可进行 STM32G474 FPU 性能的测试。, X& r8 U- l8 e3 T
3 L, C4 O4 E" }! B8 t2.5. 编译选项 ; R1 e$ S4 }, F5 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 浮点运 算扩展)等选项,不过一般使用默认即可。
# f8 m; M& c% Q7 w5 Y9 `; I▲ 图4. KEIL 工程,编译选项设置 03使用 KEIL Trace 工具进行测量
; h! o" f+ V, A8 k9 P) ]3.1. KEIL 工程设置
0 ?4 j# [* i) g; U5 ]7 N: w
' i* j% @9 E2 ^KEIL 工程下,首先选择工程选项设置,在 Debug 选项页中,右上部使用 Debugger 工具栏中选 Settings,如下图 5 和图 6 设置。注意 KEIL Trace 设置的时钟必须要与实际 STM32 使用的系统时钟相一致,如图 6 中,STM32G474 使用了 170MHz 的系统时钟, KEIL Trace 中也要相应设置为 170MHz。3 y) Z8 D* l0 ^7 F
▲ 图5. KEIL 工程,Debugger 设置入口 ▲ 图6. KEIL 工程,Cortex-M Trace 功能设置 运行KEIL debugger,如下图7所示,将断点设置在要测量的语句前及其后,执行 代码,当Debugger停在断点时,其状态栏中t1指示的即为当前代码的已执行时间。测试代码起止时间差即为代码执行用时。该Trace功能计时是比较准确的。当然如果您希望掌控更多,也可以通过代码来实现,如增加诸如如下代码:
7 V2 o% P1 G# i! G1 h' G" VnStart = DWT->CYCCNT;
# z) M3 V& X0 b5 e8 y~~~需测试执行时间的代码~~~
8 j- c7 X b: q2 _2 H" W0 HnStop = DWT->CYCCNT;
- K0 ^& @! E x. N' s; ]0 T; J1 H然后用(nStop – nStart)/系统时钟,换算成时间即可。(我们这里没有考虑中断,一 般测量前需要禁用中断)
; e+ ?% q; K' b2 o3 v4 c▲ 图7. KEIL 工程,Debug 模式下 Trace 程序执行时间 3.2. 测试结果
* u: K$ i, ^; T7 U) D% h下表列出了STM32G474 10K次 浮点“乘”用时统计。' C, M! j/ r, n [2 N
▲ 表1. STM32G474 10K 次 浮点“乘”用时统计表 10 X 1024次浮点乘
' c- P4 S4 }! E0 r6 k9 Q
$ B+ [$ r: k. X8 J1 e增加--loop_optimization_level=2 编译选项
% H3 n) D% x) q" P, O: R! g+ G! O3 r& Q$ I- {" x; x
FPU 核心汇编代码的比较,见图8和图9。9 _; d+ v: {, \: |+ I) h$ {3 r
▲ 图8. 使用--loop_optimization_level=2 编译选项的常规代码汇编 ▲ 图9. ARM DSP 库 arm_mult_f32 函数汇编 1 N! s- ^; [3 ~2 _+ J
$ O5 k, R, p6 y; V- O$ Y2 Z: a7 C @
使用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是很高效的。0 p. b! Q7 s3 p$ \5 P
* ]% h0 E a2 p9 v, R* J1 {; ~04小结
- `" s( [5 w% V0 `' m本文介绍了使用 STM32G474 FPU 进行浮点运算,从系统的角度、ARM DSP Lib、 编译选项的影响等几个方面探讨如何提升整体性能,并介绍了如何利用 KEIL Trace 工具进 行测量。以供在系统性能方面有需求的客户参考借鉴。
* j4 _* U* u0 I6 K6 N5 ~' j, @3 x R7 e& Z
; Z- M& t1 K( a* }/ S$ |
如果你有其他想要的实战笔记,可评论区留言,管管来跟工程师沟通!
9 l \) D& l S
- G7 J" B$ i' A. b8 O! `: i/ a; @% x- l" }6 t
/ J( t! c8 g$ U3 [* ~5 {* m+ `& s r
' e. j, b+ K( l1 I$ G( Y1 }- G
& _ v' ^% u% b' `2 L1 w# a! J/ Y7 ]$ K) b4 M$ ]2 P
5 x, c2 M& s- C' H$ ^- h% z: d, N, ^+ w& `: @
X+ f( t6 M; }$ @, u5 \( `/ a6 R0 G. i" b* S4 n' w% O7 U( x
! ^ w6 i2 g% f; l
$ G- Y9 y2 a( s8 L/ T- {5 \8 I3 Q# @
|