8 v* L* u$ N5 H. E
01引言4 r: E+ T3 _0 b' J7 |
客户在使用 STM32G474 时,希望使用 FPU 进行浮点运算,并最大化其性能。本文 从 STM32G474 系统的角度、ARM DSP Lib、编译选项的影响等几个方面探讨如何提升整体性能,并介绍如何使用 KEIL 工具进行测量。" k, _! C: a) C! W w
/ \) U& P7 E# R5 }( g8 W02STM32G474 FPU 运算性能优化
# q) H. P; ^) C7 j0 [2.1. STM32G474 系统性能优化
! |2 a4 [& i/ B8 N$ o
* {2 Q* _7 A o& S1 h$ @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 并发访问产生的性能下降。应用则需要根据实 际情况,合理使用内存区域。/ E1 g3 e! z- W0 ^ e6 g0 D+ n: f9 K
▲ 图1. STM32G474 架构 6 u+ B1 ` |" e' ~: d1 O9 j
# ~$ Y+ A! u c9 K/ D3 F2 H) S' d2.2. ARM DSP Lib 的使用 1 l0 q& `: k6 M( S; P& I
在 ARM DSP 库实现了很多 math 算法,可进行浮点乘加、点积、卷积、FFT、NN 等 多种算法 API,可以使用 ARM DSP 库高效使用 FPU。ARM DSP 代码位置如下:
5 a5 C' y1 N9 D# s) m! ~ ], M5 c; _) t: J8 }0 y
2.3. 示例代码 4 Y% z$ o! @# m% i$ y5 C$ F1 `
下面示例代码中对浮点乘法运算进行了测试。用户可以使用 STM32CubeMX 生成 STM32G474 KEIL 工程,在 main.c 文件中加入如下示例代码:
& f+ O1 R% |7 n2 m# E# f2 r) P1 x
) \, J! f# Q: Z5 r- __attribute__((section (".TEST_INPUT_A"))) float32_t testInputA[1024] = 5 A: L7 y+ k8 ~$ N, ~( C
- {
T* r' _8 _( o% {% B& d% m5 ]8 j$ g - 0.623234f, 0.799049f, 0.940890f, -0.992092f, 0.212035f, 0.237882f, -9 o# _2 x. g4 s. J# X
- 1.007763f, -0.742045f,
$ t; [6 `: d( W! L- r - ~~ 这里数组使用动态生成的float数据,数据量较大,略2 O: H* |% W9 {" ~1 _
- -0.417470f, -0.205806f, -0.174323f, 0.217577f, 1.684295f, 0.119528f,
9 B; u, M$ O/ b) y( x - 0.650667f, 2.080061f2 @7 v2 t/ e) v2 {& r- n1 R' J* f
- };
0 Q2 h) r7 n3 v4 o- @, z4 f9 _ - __attribute__((section (".TEST_INPUT_B"))) float32_t testInputB[1024] =
7 B0 H6 R" j. h7 N' M- i - {( v& V j+ \8 H, o( ?: }! N0 {
- -2.423957f, -0.223831f, 0.058070f, -0.424614f, -0.202918f, -1.513077f, -5 @8 i8 @- L' D2 e& u9 \8 |$ i
- 1.126352f, -0.815002f,, n8 l* l# q/ L* f Z4 ]
3 \- r+ n) I0 ?, u% {8 k- ~~ 这里数组使用动态生成的float数据,数据量较大,略
5 {0 f, H: [: o - -0.447001f, -0.725993f, 0.354045f, -0.506772f, -2.103747f, -0.664684f, 1.450110f, -0.329805f
1 {8 H8 y; |4 O6 ~! _* `; q- ?* | - };
* J, R9 G3 J# v- ~3 O8 j
. {/ @% |, z* G/ U: ?8 [+ _- __attribute__((section (".TEST_RESULT_D"))) float32_t testResult[1024];
0 L4 B. W" ]$ H' j4 q7 t2 [" A
( C0 ?6 ?- W: |4 u# ~7 l- float32_t* pA;: P9 X3 J* K8 z
- float32_t* pB; / o3 G2 y/ O0 e" K5 v) A
- float32_t* pR;
, a8 j/ K+ o, G! M5 b9 {! W - /* Private user code --------------------------------------------------*/ . z# w% t% I G7 l# n& \. p" s' {3 z
- /* USER CODE BEGIN 0 */
$ `- k6 k K, U8 G0 W3 B - void test_normal_mul(uint32_t kLoops, float32_t* $ E, }2 \& s4 A0 j0 w! q
- pSrcA, float32_t* pSrcB, float32_t* , d8 T( G4 l1 X$ M+ E( q
- pResult, uint32_t lenVector)
3 g4 t" C1 J# x5 S: @' o - {
1 X* {8 J( D1 C# u& P8 z; k4 `; P - for (uint32_t j = 0; j < kLoops; j++) ( D" x6 G1 H2 D: G
- { 8 l+ L8 }! _/ o0 b9 N! D2 d
- pA = pSrcA; $ P- o. w$ ^! l+ | X0 M
- pB = pSrcB;
4 ]- `9 w* S5 U# v - pR = pResult; 9 x% h! Q L5 [/ [
6 J" u) ]% |, _8 B. N* o. Y4 X- for (uint32_t i = 0; i < lenVector; i++)
0 W9 V* M: I/ ~0 j8 l& E/ n. D/ `, O - { *pR++ = (*pA++) * (*pB++) ; ) z/ h( T! m# _: N) |
- } . s) I% ?7 d( U4 l
- } 0 o$ }! _* y+ B& I/ F ]2 \
- } 9 s1 u' P. v( `$ y- {: \8 | o
3 D9 {% G7 V/ ~ L. i3 G1 a- #if defined (__FPU_USED) && (__FPU_USED == 1U) . S" X4 I4 J( s& I( _; {
- /* Use arm dsp lib to test basic operation Multiply, FPU enabled */ & x4 k" B; ^# _; B% s0 i4 t
- void test_arm_math_mul(uint32_t kLoops, float32_t* pSrcA, float32_t* pSrcB, float32_t* 8 p5 y9 S6 h, w/ s# @: v. Y, a. q
- pResult, uint32_t lenVector)
: u9 C0 \* d7 e! K - { 8 @5 Z' u4 m4 e ~, G. o0 V
- for (uint32_t j = 0; j < kLoops; j++)
& b4 t. s% F& K; F$ j" ]. m - { 6 x) w* f* ]2 h
- pA = pSrcA; //Code alignment with the function without FPU 4 S* `6 |7 l- e7 P1 G
- pB = pSrcB;
J+ A' k6 ~2 j- }6 v; p! U - pR = pResult;$ U8 Z% x; O6 H6 D$ K( L2 R+ _
- arm_mult_f32(pA, pB, pR, lenVector); " m# Y" L0 p) q* |* f
- } # {8 a) F- A5 v4 x
- } - }, |( B% W1 ]3 x
- #endif
% V$ \/ K/ T e" m - 9 I0 j& Y. a* }: y! D. L
- /**
f# r& g; c6 h4 J1 [* g - * @brief The application entry point.
% Z5 [. V; n& T2 U8 [7 { - * @retval int + E9 T0 d, a% S: u9 ]( S
- */
7 t: H& D" N, M# d/ h l. F* Q3 \ - int main(void)
, S ^ y: ]* v - {
! M* E2 r; G9 N, l ^6 ~# h' ^ { - /* MCU Configuration------------------------------------------------*/
3 r& t, P$ j( [ - ( Y2 i2 L5 B# [; Y) ?" O
- /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
+ X$ k' V+ w3 o' Y4 L - HAL_Init();
# }) U+ w9 a0 v+ n6 A
$ u. }# X8 n: z, [& E( l- /* Configure the system clock */ . _5 Q1 ?1 @; c( m( t" E7 c
- SystemClock_Config(); , W0 f4 E% e% ~3 V( M+ ]6 {+ D
+ L6 Y4 I+ |& m8 h, y7 P- …
, b4 B7 s! s3 ]# C9 Q
+ N7 l8 ~' t$ ?$ H; a- HAL_Delay(100); 8 X8 o+ r& {# w
% _% b8 e8 o' f/ D- /* USER CODE BEGIN 2 */
( T4 X4 R2 Z- H1 K" g/ D) C! \ - test_normal_mul(10, testInputA, testInputB, testResult, 1024);: I) l( G a, x/ w) x( i
- test_normal_mul(10, testInputA, testInputB, testResult, 1024);
, l. |+ A% U# ^* w1 R
2 T- \" Y$ U$ s! L0 D9 o6 t- #if defined (__FPU_USED) && (__FPU_USED == 1U) 2 C& T+ H, M w/ {& ~% c, R
- // Multiply calculation with arm dsp lib
; S0 u: D2 ?1 f. R6 ] - test_arm_math_mul(10, testInputA, testInputB, testResult, 1024);
- F* Q+ t! _" i8 K - test_arm_math_mul(10, testInputA, testInputB, testResult, 1024);
2 L! T& g+ r2 `1 _& x - #endif
$ T- l: I# L- ~ - $ r9 o# \$ f7 j! z1 S
- /* USER CODE END 2 */ ' i3 I; B! J. `: j Q
! C8 N5 W4 R) ?/ Q- /* Infinite loop */ ' g% P5 H, l( k$ x& ~* o
- /* USER CODE BEGIN WHILE */
" V" m3 k* r; g4 B: n% G5 q+ q - while (1)
+ m# o2 w2 z) _6 `$ f - {
O0 _3 D( C& s( M- j, y+ v$ S/ h - /* USER CODE END WHILE */
* L5 J* b( Y$ \* [ _
# ^( r- b9 D. O- /* USER CODE BEGIN 3 */
. O1 ^+ y, ?, m% ^: I8 J - }
9 p$ }! ]' }* J - /* USER CODE END 3 */ * f7 R) o- `# b& v. T# F# ]
- }
复制代码 2.4. 工程配置 1 m4 T- {- v! f
通过 KEIL 工程 Options / Target, Floating Point Hardware, 确定 FPU On/Off。
8 b z S% G) w3 @' M
2 [8 Q1 J: b" ^+ B6 P1 ?1 u
5 W! V0 r/ \( i$ D1 H1 [6 m▲ 图3. KEIL 项目工程 FPU 单精度浮点设置 通过 STM32G474_FPU_TEST.sct 文件配置 Data 存放区域,如下例,将测试数据置 于 SRAM2。
p' |) \9 w* x/ R9 q$ e! j- : A1 ^( _2 q6 j0 v2 E r: j5 `
- RW_IRAM1 0x20000000 0x00014000 { ; RW data8 r X3 a; N/ V; v; _, D& V& S' u7 P
- .ANY (+RW +ZI)
/ j: r7 l& U0 T# b9 K$ Z - }: O' l& ?2 T6 C0 c
- RW_IRAM2 0x20014000 0x00004000 {
" c/ d# ^% Q7 }& |+ o+ m - *(.TEST_INPUT_A): B5 ?9 M b" j: f4 h
- *(.TEST_INPUT_B)
7 F: d- O" h. ?; a6 @8 y0 y - *(.TEST_RESULT_D)
$ E0 u3 S1 K( x" B* [; Q - }7 ]# }! N6 d4 m3 L- H+ t- s
- RW_CCM 0x20018000 0x00008000 {* p/ |* ?+ w+ H8 \* l/ z% r
- }
复制代码 完成后,进行编译链接,即可进行 STM32G474 FPU 性能的测试。+ I/ ?7 X& O+ L; X) q% H- X2 r! y4 W
4 l" X0 S& G4 i/ L2.5. 编译选项 ; l) ^5 g4 V: G$ Z' H. u
本文中我们使用的是 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 浮点运 算扩展)等选项,不过一般使用默认即可。0 G' O, e) l$ R
▲ 图4. KEIL 工程,编译选项设置 03使用 KEIL Trace 工具进行测量 3 R& A9 w3 j' F+ \
3.1. KEIL 工程设置
/ }# O3 r: m: F6 s( ~) o# {
* V- `' r, e) a/ L) x) y, pKEIL 工程下,首先选择工程选项设置,在 Debug 选项页中,右上部使用 Debugger 工具栏中选 Settings,如下图 5 和图 6 设置。注意 KEIL Trace 设置的时钟必须要与实际 STM32 使用的系统时钟相一致,如图 6 中,STM32G474 使用了 170MHz 的系统时钟, KEIL Trace 中也要相应设置为 170MHz。) O) H) {: j, T$ o
▲ 图5. KEIL 工程,Debugger 设置入口 ▲ 图6. KEIL 工程,Cortex-M Trace 功能设置 运行KEIL debugger,如下图7所示,将断点设置在要测量的语句前及其后,执行 代码,当Debugger停在断点时,其状态栏中t1指示的即为当前代码的已执行时间。测试代码起止时间差即为代码执行用时。该Trace功能计时是比较准确的。当然如果您希望掌控更多,也可以通过代码来实现,如增加诸如如下代码: B# }1 G% A2 e, {1 K" `, w) {
nStart = DWT->CYCCNT; , Z: E6 `5 g9 u1 c+ _" l
~~~需测试执行时间的代码~~~
' y& C9 `* r$ X b K2 \% ?nStop = DWT->CYCCNT;
8 p5 |: |' t3 l5 `3 [然后用(nStop – nStart)/系统时钟,换算成时间即可。(我们这里没有考虑中断,一 般测量前需要禁用中断)
) T0 @8 t2 k/ C5 Y7 V2 y▲ 图7. KEIL 工程,Debug 模式下 Trace 程序执行时间 3.2. 测试结果
) J* H m8 b- T5 y) `0 H下表列出了STM32G474 10K次 浮点“乘”用时统计。0 e7 {' a: M( R
▲ 表1. STM32G474 10K 次 浮点“乘”用时统计表 10 X 1024次浮点乘* W1 }6 r5 I6 T$ A+ V3 M4 s4 n* V( U* L
+ y0 g; A/ q5 ^$ M9 K" W增加--loop_optimization_level=2 编译选项
$ }" g! _: R6 z) _1 i) K
! P: A- x P/ k( I8 A5 t- V7 m8 I J1 zFPU 核心汇编代码的比较,见图8和图9。
% \) \( I* H) j7 A) V$ G t▲ 图8. 使用--loop_optimization_level=2 编译选项的常规代码汇编 ▲ 图9. ARM DSP 库 arm_mult_f32 函数汇编 1 Q8 q, c7 G% q1 [
4 c/ [2 h1 G% t& V" x2 E
使用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是很高效的。
4 j4 \+ C; n: B$ g2 C/ K! ?: Q% t: l" I4 H# p# n
04小结 3 ]+ E! z, y/ `! V9 E1 Q" e0 s
本文介绍了使用 STM32G474 FPU 进行浮点运算,从系统的角度、ARM DSP Lib、 编译选项的影响等几个方面探讨如何提升整体性能,并介绍了如何利用 KEIL Trace 工具进 行测量。以供在系统性能方面有需求的客户参考借鉴。
7 ]" a! t/ d4 t! m
- D) R- w- w( g6 Z) D0 O
) ?% I: ~5 h$ p9 n) s' p如果你有其他想要的实战笔记,可评论区留言,管管来跟工程师沟通!1 r8 {" A$ @7 K, U, ]4 g
( O3 y! x$ s# ~" N! S
% E7 G& `5 Z5 H+ }! w
0 ?: t4 Z4 Y# L2 X- U) h) p5 k- N9 z1 c
8 r6 g. k, b; w: v
1 T2 @1 P/ j1 ^
$ L; X+ E! t( f1 N- ]
! q* ? L# h. A& z% |) }; [7 ^3 v, X9 U
% S) C+ W5 R7 z2 Z! f+ q
3 `6 C! H% j5 T, v, c/ d
) Q) P0 g+ o [- e Z' `, x, V, s% e2 Q/ ?; }* g
, l* j5 J, i" z- z! G' O' H
|