MCU 上的性能分析器
发表于|更新于
|总字数:36k|阅读时长:2:11分钟
性能分析器可以大致划分为采样(sampling)分析器和追踪(tracing)分析器两类。采样分析器通过定时采样 PC,再根据 PC 反推得到代码热点,对业务代码入侵较小。而追踪分析器则通过插桩或硬件手段观测特定事件发生的频次和耗时,不会遗漏任何事件,但对业务代码入侵较大。
在 Cortex-M 上,发生异常时硬件会自动压栈^1:

我们只需要根据 LR[2],找到 MSP/PSP+6 就可以得到 PC 的值了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| uint32_t pc_sample_buf[64]; volatile uint32_t sample_index = 0;
__attribute__((naked)) void TIM3_IRQHandler(void) { __asm volatile( "tst lr, #4 \n" "ite eq \n" "mrseq r0, msp \n" "mrsne r0, psp \n" "b tim3_irq_handler_c \n"); }
void tim3_irq_handler_c(uint32_t *stack) { uint32_t pc = stack[6]; pc_sample_buf[sample_index++] = pc; if (sample_index >= 64) { sample_index = 0; SEGGER_RTT_WriteNoLock(0, pc_sample_buf, sizeof(pc_sample_buf)); } HAL_TIM_IRQHandler(&htim3); }
|
Tips:
如果对采样频率没有要求,也可以直接使用 SysTick,还能节约一个定时器()
随后通过 addr2line 解析地址就可以得到热点函数了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| > D:/Files/scoop/apps/python/3.13.7/python.exe d:/Files/Data/Embedded/STM32/Air32F103/perf.py === Hot Lines (Top 30) === Count Percent Location -------------------------------------------------- 9058 7.11% D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_util.c:169 6617 5.19% D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_state.c:188 (discriminator 1) 6316 4.96% D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_matrix.c:286 6168 4.84% D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_matrix.c:303 5698 4.47% D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_list_join.c:372 (discriminator 1) 5658 4.44% D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_list_join.c:398 5424 4.26% D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_list_join.c:376 (discriminator 1) 4691 3.68% D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_state.c:190 3908 3.07% D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_list_join.c:397 3839 3.01% D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_matrix.c:304 3828 3.00% D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_list_join.c:396 3026 2.37% D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_matrix.c:301 (discriminator 1) 2968 2.33% D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_matrix.c:284 (discriminator 1) 2904 2.28% D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_state.c:170 2701 2.12% D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_list_join.c:377 2559 2.01% D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_list_join.c:373 2419 1.90% D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_state.c:209 1961 1.54% D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_list_join.c:399 1956 1.53% D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_list_join.c:372 1874 1.47% D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_state.c:236 1629 1.28% D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_state.c:196 1460 1.15% D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_state.c:79 1395 1.09% D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_state.c:61 1363 1.07% D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_matrix.c:256 1350 1.06% D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_state.c:184 1348 1.06% D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_matrix.c:222 1058 0.83% D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_util.c:164 1032 0.81% D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_matrix.c:221 (discriminator 1) 1024 0.80% D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_matrix.c:301 (discriminator 3) 937 0.74% D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_state.c:277
=== Hot Functions (Top 10) === 26655 20.92% core_state_transition 19437 15.25% core_list_find 15355 12.05% core_list_reverse 15152 11.89% matrix_mul_matrix_bitextract 11566 9.08% matrix_mul_matrix 9230 7.24% crcu16 6003 4.71% matrix_sum 5943 4.66% core_bench_state 4072 3.20% core_list_mergesort 2904 2.28% ee_isdigit
|
GCC 有个拓展选项 -finstrument-functions,可以自动对函数的入口和出口进行插桩^2。
我们只需要实现这两个函数,gcc 就会帮我们完成剩下的所有工作。
1 2
| void __cyg_profile_func_enter (void *this_fn, void *call_site); void __cyg_profile_func_exit (void *this_fn, void *call_site);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| typedef struct { void *func_addr; uint32_t enter_cycle; uint64_t total_cycles; uint32_t call_count; uint32_t depth; } prof_t;
prof_t prof_table[256]; uint32_t prof_count = 0;
__attribute__((no_instrument_function)) static int32_t find_or_create_idx(void *func) { for (uint32_t i = 0; i < prof_count; i++) { if (prof_table[i].func_addr == func) return (int32_t)i; }
uint32_t primask = __get_PRIMASK(); __disable_irq();
for (uint32_t i = 0; i < prof_count; i++) { if (prof_table[i].func_addr == func) { __set_PRIMASK(primask); return (int32_t)i; } }
if (prof_count >= 256) { __set_PRIMASK(primask); return -1; }
uint32_t idx = prof_count; prof_table[idx].func_addr = func; prof_table[idx].total_cycles = 0; prof_table[idx].call_count = 0; prof_table[idx].enter_cycle = 0; prof_table[idx].depth = 0; prof_count++;
__set_PRIMASK(primask); return (int32_t)idx; }
__attribute__((no_instrument_function)) void __cyg_profile_func_enter(void *this_fn, void *call_site) { uint32_t now = DWT->CYCCNT;
int32_t idx = find_or_create_idx(this_fn); if (idx < 0) return;
prof_t *e = &prof_table[idx];
if (e->depth == 0) { e->enter_cycle = now; } e->depth++; }
__attribute__((no_instrument_function)) void __cyg_profile_func_exit(void *this_fn, void *call_site) { uint32_t now = DWT->CYCCNT;
int32_t idx = find_or_create_idx(this_fn); if (idx < 0) return;
prof_t *e = &prof_table[idx]; if (e->depth == 0) return;
e->depth--; if (e->depth == 0) { e->total_cycles += (uint32_t)(now - e->enter_cycle); e->call_count++; } }
|
Tips:
好像有点数据竞争,但在单核 MCU 上问题不大(逃
同理,得到采样结果后通过 addr2line 即可找到原始函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| > D:/Files/scoop/apps/python/3.13.7/python.exe d:/Files/Data/Embedded/STM32/Air32F103/prof_tracing.py === Profile Tracing Results === Func Addr | Call Cnt | Total Cycles | Total Time | Function | Location -------------------------------------------------------------------------------------------------------------- 0x08003661 | 1 | 2302206491 | 10.658 s | core_main | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_main.c:89 0x080035DD | 1 | 2302085863 | 10.657 s | iterate | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_main.c:38 0x08002FC1 | 1000 | 2300431388 | 10.650 s | core_bench_list | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_list_join.c:132 0x08002965 | 1501 | 2051300729 | 9.496 s | core_list_mergesort | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_list_join.c:426 0x08002B69 | 55544 | 2004982465 | 9.282 s | cmp_complex | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_list_join.c:101 0x08002739 | 111088 | 1970040210 | 9.120 s | calc_func | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_list_join.c:63 0x080048FD | 2000 | 1828036930 | 8.463 s | core_bench_state | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_state.c:45 0x080048FD | 2000 | 1828036930 | 8.463 s | core_bench_state | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_state.c:45 0x080046B1 | 512000 | 1492653333 | 6.910 s | core_state_transition | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_state.c:184 0x080046B1 | 512000 | 1492653333 | 6.910 s | core_state_transition | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_state.c:184 0x08004681 | 1960000 | 643026593 | 2.976 s | ee_isdigit | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_state.c:168 0x08004681 | 1960000 | 643026593 | 2.976 s | ee_isdigit | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_state.c:168 0x08004D81 | 131004 | 206165225 | 954.468 ms | crc16 | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_util.c:176 0x08004BD9 | 146004 | 158889680 | 735.600 ms | crcu16 | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_util.c:166 0x08004F41 | 32000 | 129106003 | 597.712 ms | crcu32 | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_util.c:171 0x08004F41 | 32000 | 129106003 | 597.712 ms | crcu32 | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_util.c:171 0x08004F41 | 32000 | 129106003 | 597.712 ms | crcu32 | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_util.c:171 0x08004519 | 2000 | 97517187 | 451.468 ms | core_bench_matrix | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_matrix.c:81 0x08004069 | 2000 | 92507840 | 428.277 ms | matrix_test | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_matrix.c:117 0x08004ACD | 292008 | 63374749 | 293.401 ms | crcu8 | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_util.c:143 0x080028CD | 103000 | 50244289 | 232.612 ms | core_list_find | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_list_join.c:370 0x0800292D | 102000 | 42948749 | 198.836 ms | core_list_reverse | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_list_join.c:395 0x08003F55 | 2000 | 30213199 | 139.875 ms | matrix_mul_matrix | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_matrix.c:279 0x08003FD9 | 2000 | 20815571 | 96.368 ms | matrix_mul_matrix_bitextract | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_matrix.c:296 0x08003FD9 | 2000 | 20815571 | 96.368 ms | matrix_mul_matrix_bitextract | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_matrix.c:296 0x08003FD9 | 2000 | 20815571 | 96.368 ms | matrix_mul_matrix_bitextract | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_matrix.c:296 0x080027E5 | 104148 | 14062905 | 65.106 ms | cmp_idx | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_list_join.c:112 0x08003DC1 | 8000 | 11614225 | 53.769 ms | matrix_sum | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_matrix.c:216 0x08003EA9 | 4000 | 3831377 | 17.737 ms | matrix_add_const | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_matrix.c:252 0x08003EF9 | 2000 | 2172907 | 10.059 ms | matrix_mul_vect | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_matrix.c:265 0x08003E4D | 2000 | 1964764 | 9.096 ms | matrix_mul_const | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_matrix.c:240 0x0800289D | 1000 | 362362 | 1.677 ms | core_list_undo_remove | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_list_join.c:345 0x08002861 | 1000 | 356588 | 1.650 ms | core_list_remove | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_list_join.c:316 0x08002861 | 1000 | 356588 | 1.650 ms | core_list_remove | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_list_join.c:316 0x08002C35 | 1 | 50889 | 0.235 ms | core_list_init | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_list_join.c:213 0x08002AF5 | 32 | 11127 | 0.051 ms | core_list_insert_new | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_list_join.c:283 0x08004555 | 1 | 6420 | 0.029 ms | core_init_state | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_state.c:116 0x0800283D | 29 | 3528 | 0.016 ms | copy_info | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_list_join.c:120 0x08003CC5 | 1 | 2693 | 0.012 ms | core_init_matrix | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_matrix.c:165 0x08005345 | 4 | 1925 | 0.008 ms | time_in_secs | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_portme.c:91 0x08005311 | 1 | 906 | 0.004 ms | get_time | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_portme.c:81 0x08004A31 | 5 | 847 | 0.003 ms | get_seed_32 | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_util.c:39 0x0800539D | 1 | 783 | 0.003 ms | portable_fini | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_portme.c:117 0x08004BB5 | 1 | 740 | 0.003 ms | check_data_types | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_util.c:180 0x080052E1 | 1 | 719 | 0.003 ms | stop_time | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_portme.c:69 0x08005375 | 1 | 289 | 0.001 ms | portable_init | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_portme.c:103 0x080052B1 | 1 | 287 | 0.001 ms | start_time | D:\Files\Data\Embedded\STM32\Air32F103/User/lib/CoreMark/core_portme.c:60
|
需要注意的是追踪分析器对性能的影响非常大。以 Air32 而言,开启插桩后 CoreMark 分数只有正常的十分之一不到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| # none 2K performance run parameters for coremark. CoreMark Size : 666 Total ticks : 15019 Total time (secs): 15 Iterations/Sec : 533 Iterations : 8000 Compiler version : GCC15.2.1 20251203 Compiler flags : "-O3" Memory location : STACK seedcrc : 0xE9F5 [0]crclist : 0xE714 [0]crcmatrix : 0x1FD7 [0]crcstate : 0x8E3A [0]crcfinal : 0x5275 Correct operation validated. See readme.txt for run and reporting rules.
# sampling (10k) 2K performance run parameters for coremark. CoreMark Size : 666 Total ticks : 15135 Total time (secs): 15 Iterations/Sec : 533 Iterations : 8000 Compiler version : GCC15.2.1 20251203 Compiler flags : "-O3" Memory location : STACK seedcrc : 0xE9F5 [0]crclist : 0xE714 [0]crcmatrix : 0x1FD7 [0]crcstate : 0x8E3A [0]crcfinal : 0x5275 Correct operation validated. See readme.txt for run and reporting rules.
# tracing 2K performance run parameters for coremark. CoreMark Size : 666 Total ticks : 10658 Total time (secs): 10 Iterations/Sec : 50 Iterations : 500 Compiler version : GCC15.2.1 20251203 Compiler flags : "-O3" Memory location : STACK seedcrc : 0xE9F5 [0]crclist : 0xE714 [0]crcmatrix : 0x1FD7 [0]crcstate : 0x8E3A [0]crcfinal : 0xA14C Correct operation validated. See readme.txt for run and reporting rules.
|
完整代码:
GitHub