性能分析器可以大致划分为采样(sampling)分析器和追踪(tracing)分析器两类。采样分析器通过定时采样 PC,再根据 PC 反推得到代码热点,对业务代码入侵较小。而追踪分析器则通过插桩或硬件手段观测特定事件发生的频次和耗时,不会遗漏任何事件,但对业务代码入侵较大。

在 Cortex-M 上,发生异常时硬件会自动压栈^1:
img

我们只需要根据 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; // never entered

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