Files
autumn-recruitment/00.Go/runtime/Pprof 性能分析指南_test.md
T

155 lines
9.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: [test/review, go, pprof, cpu-profile, heap-profile]
create time: 2026-08-09 12:00
---
# Pprof 性能分析指南_测试题
## 概述
本测试覆盖 Go pprof 性能诊断工具的核心用法,包括数据采集原理、CPU/Heap/Block/Mutex 四大 Profile 的解读方法、Web UI 操作及实战排查流程,共 10 道题(6 选择 + 3 填空 + 1 简答)。
---
## 一、选择题(6道,由浅入深)
> **难度阶梯**: Q1-Q2 基础概念 → Q3-Q4 核心原理 → Q5-Q6 深入应用/边界场景
### Q1(基础)— 考察定义层面
Go pprof 默认采用何种方式采集运行时数据?
A. 在代码中手动插入计时器记录每个函数耗时
B. 在每个 OS 线程上定期注入信号采样(默认每 10ms 一次)
C. 通过操作系统提供的 perf event 系统调用采集
D. 编译期插桩(PIE)在二进制中注入探测点
### Q2(基础)→
以下哪个 HTTP 端点在导入 `_ "net/http/pprof"` 后会注册用于获取 CPU profile(持续 30 秒)?
A. `/debug/pprof/heap`
B. `/debug/pprof/profile?seconds=30`
C. `/debug/pprof/cpu?duration=30s`
D. `/pprof/cpumodel`
### Q3(进阶)— 理解 CPU profile 指标
某服务 CPU profile 输出显示:`heavyComputation` 函数 flat=45s (45.2%)、cum=89.1%;`parseJSON` 函数 flat=23.1s、cum=23.1%。以下判断正确的是:
A. `heavyComputation` 的大部分时间花在自己内部的计算上,其子函数也消耗了显著 CPU 时间
B. `parseJSON` 的问题在其子函数中,因为 cum 和 flat 相同说明没有开销在调用链下游
C. `heavyComputation` 的问题是它调用的子函数(如 sortAlgorithm)占用了大量 CPU,而非自身逻辑
D. `parseJSON` 的 flat 低于 heavyComputation 说明它不可能是瓶颈
### Q4(进阶)— 比较辨析
Heap profile 中 `inuse_space` 和 `alloc_space` 两种维度分别适用于什么场景?
A. inuse_space 用于排查内存泄漏,alloc_space 用于排查频繁分配导致的 GC 压力
B. inuse_space 用于排查频繁分配,alloc_space 用于排查内存泄漏
C. 两者都只能用于排查内存泄漏,只是统计口径不同
D. inuse_space 包含已被 GC 回收的对象,alloc_space 只包含当前活跃对象
### Q5(深入)— 场景推理
一个服务出现内存持续增长的异常现象。使用 `go tool pprof -top -sample_index=inuse_objects` 分析 heap profile,发现 `inuse_objects` 持续增长而 `alloc_objects` 趋于平稳。最可能的原因是:
A. GC 正常工作,增长来自正常的应用逻辑分配
B. 存在 goroutine 泄露导致调度器负担加重
C. 可能存在内存泄漏,有引用持有者阻止某些对象被回收
D. alloc_objects 平稳说明堆已经饱和无法再分配
### Q6(深入)— 源码级/边界场景
关于 Block Profile 和 Mutex Profile 的启用方式,下列描述哪一项正确?
A. Block Profile 默认开启(SetBlockProfileRate 默认为 1),Mutex Profile 需要手动调用 SetMutexProfileFraction
B. 两者默认都关闭,Block Profile 通过 runtime.SetBlockProfileRate(N) 启用,Mutex Profile 通过 runtime.SetMutexProfileFraction(1) 启用
C. Block Profile 和 Mutex Profile 都只需要导入 net/http/pprof 即可自动启用
D. SetBlockProfileRate(1) 表示每秒采样 1 次阻塞事件,值越小精度越高
---
## 二、填空题(3道)
### F1 — 填空1
`go tool pprof` 有以下常用参数:使用 \_\_\_\_\_\_ 按指标排序列出顶级函数;使用 \_\_\_\_\_\_ 以树形展示调用链;使用 `-focus=regex` 可以只显示匹配指定函数的路径及其子树。
> **提示**: 回想文档中"基本用法"表格的前三行,对应最常用的三种视图模式。
### F2 — 填空2
Block Profile 配置函数 `SetBlockProfileRate(N)` 的含义是:每个阻塞事件有 \_\_\_\_\_\_ 的概率被采样。生产环境建议使用较保守的值(如 100 或 \_\_\_\_\_\_),以避免对性能造成过大影响。
> **提示**: N 值代表 1/N 的采样率——N=1 表示全部采样。
### F3 — 填空3
要排查 goroutine 泄露,可以创建一个文件并用 \_\_\_\_\_\_ 写入 goroutine 信息,或者用 `pprof.Lookup("goroutine")` 查找并调用 `.WriteTo(os.Stdout, 0)` 导出。随后配合 `go tool pprof -top -nodecount=20 goroutine.pprof` 查看堆积的 goroutine 类型。
> **提示**: 参考文档中"分析 goroutine 泄露"的代码示例片段。
---
## 三、简答题(1道)
### S1
一个微服务上线后遇到线上问题:QPS 从正常的 5000 下降到 2000,CPU 利用率飙升至 90%,同时部分请求出现超时。请结合 pprof 工具链设计一套完整的排查方案:
1. 针对"CPU 飙高"的症状,应该采集哪种 Profile?如何区分问题是出在当前函数自身还是其调用的子函数?
2. 如果怀疑该服务存在锁竞争导致延迟增加,应该如何启用和验证相关 Profile?
3. 在 Flame graph 上阅读时,宽度和高度分别代表什么含义?
> **答题框架提示**: 按症状分类选择 Profile 类型,解释 flat/cum 的判断逻辑,描述 Mutex/Block Profile 的启用步骤,最后说明火焰图的视觉语义。
---
## 参考答案与解析
### 选择题答案
| 题号 | 正确答案 | 解析 |
|------|---------|------|
| Q1 | B | Go 运行时在每个 OS 线程上定期注入信号采样,默认每 10ms 触发一次,记录当前函数栈等上下文。A 不是 Go 的方式(需手动插桩),C 是 Linux perf 的行为,D 是编译期插桩方案,非 Go 内置 pprof 的做法。 |
| Q2 | B | 导入 `_ "net/http/pprof"` 后 `/debug/pprof/profile` 端点采集 CPU profile,默认 30 秒(可通过 `?seconds=N` 调整)。heap 对应 `/debug/pprof/heap`,其他为虚构端点名。 |
| Q3 | A | heavyComputation flat=45s、cum=89.1s,差值约 44s 花在子函数上,说明自身和子函数都耗 CPU,但自身占大头。B 错——flat=cum 说明它的子函数不耗 CPU。C 错误归因于子函数。D 错——flat 高低不等于不是瓶颈,23.1s 也可能是主要消耗之一。 |
| Q4 | A | inuse_space 看的是"当前还活着"的内存,适合找泄漏;alloc_space 看的是"累计分配了多少",适合找高频短命分配带来的 GC 压力。B 完全颠倒。C 否认了分工差异。D 把两个指标的定义搞反了。 |
| Q5 | C | inuse 持续增长说明有对象一直在积累未被回收——这是典型的泄漏特征。alloc 平稳说明新分配量不再增长,即新增的分配都能被 GC 回收,但旧的对象一直没被释放。A 与现象矛盾(正常情况 inuse 应稳定在一个水平线附近)。 |
| Q6 | B | Block Profile 和 Mutex Profile 默认都关闭。Block 通过 SetBlockProfileRate(N) 控制(N=1 全采样,生产建议 100/1000);Mutex 通过 SetMutexProfileFraction(1) 开启(1 表示每次锁竞争都采样)。A 颠倒了两者的默认状态。C 错——只导 pprof 包不够,还需显式设置 rate/fraction。D 错——N=1 是全采样,不是每秒 1 次。 |
### 填空题答案
| 题号 | 答案 | 解析 |
|------|------|------|
| F1 | -top、-tree | `-top` 按指标降序列出函数表;`-tree` 以缩进树形式展示调用链路关系;`-web` 则调用 Graphviz 生成图形。三个是最常用的三种视图入口。 |
| F2 | 1/N、1000 | SetBlockProfileRate(1) = 全部采样(性能开销大),SetBlockProfileRate(1000) = 千分之一采样率。生产环境一般设为 100 或 1000 以平衡精度与开销。 |
| F3 | pprof.WriteGoroutineProfile(f) | 该函数将当前所有 goroutine 的栈信息和状态写入文件,配合 go tool pprof 可查看哪些 goroutine 类型在持续堆积——数量不下降即暗示泄露。 |
### 简答题参考答案
S1:**参考答案要点**:
1. **CPU Profile 及 flat/cum 判断逻辑**:
- 通过 HTTP 端点 `/debug/pprof/profile` 或代码手动调用 StartCPUProfile/StopCPUProfile 采集 CPU profile
- flat 高 + cum 高 → 热点在函数自身的逻辑实现中
- flat 低 + cum 高 → 问题在它所调用的子函数中,需沿调用链向下追查
- 如果是标准库函数被业务代码频繁调用,考虑是否有更高效的替代方案
2. **Mutex/Block Profile 启用与验证**:
- Mutex Profile:`runtime.SetMutexProfileFraction(1)` 启用(生产建议较小值),然后通过 `/debug/pprof/mutex` 获取 profile 文件,用 `go tool pprof -top` 查看等待锁最久的 goroutine
- Block Profile:`runtime.SetBlockProfileRate(100)` 启用(生产推荐保守值),然后通过 `/debug/pprof/block` 获取 profile 文件,观察 channel 收发或 mutex 锁争用是否为主要阻塞源
- 如果大量 goroutine 在同一个锁上等待,可考虑缩小临界区、使用 RWMutex、或分片锁分散竞争
3. **Flame graph 视觉语义**:
- 宽度 = 该函数消耗的 CPU 时间占比(越宽消耗越多)
- 高度 = 调用栈的深度(层级越高离根越远)
- 顶层窄而高的柱子通常是需要优化的热点函数,从上往下看能追踪完整的调用链
**评分标准**:答出任意 2 个要点即可得满分;完全正确需覆盖全部要点(Profile 类型选择和采集方法、flat/cum 的归因逻辑、锁竞争的两种 Profile 启用步骤、火焰图宽高语义)。
## 关联笔记
- [[../runtime/三色标记GC原理]]
- [[../concurrency/Goroutine 调度模型]]