155 lines
9.3 KiB
Markdown
155 lines
9.3 KiB
Markdown
|
|
---
|
|||
|
|
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 调度模型]]
|