第2章 性能分析工具链
做推理性能优化,第一件事不是调参数,而是「看清楚」瓶颈在哪。我见过太多人上来就改 batch size、换量化精度,折腾半天发现瓶颈在磁盘 I/O 或者 Python GIL 上。所以这一章,咱们就把手头的工具捋一遍——Linux 系统级、Python 进程级、深度学习框架级,三层工具链,一个都不能少。
2.1 Linux 性能分析工具:先看系统,再看进程
我个人习惯,排查任何推理服务性能问题,第一件事就是 top 敲下去。别笑,真的。看一眼 CPU 是不是跑满,内存有没有 leak,swap 有没有飙高。但 top 的信息太糙了,所以我更喜欢 htop——颜色分区、树状进程、鼠标操作,舒服很多。
htop 按 F6 排序,选 PERCENT_CPU,瞬间找出哪个进程在吃资源。我曾经在生产环境发现一个 Python 进程 CPU 跑到 800%,结果是个死循环的预处理脚本。
但光看进程不够,你得知道 CPU 到底在忙什么。这时候 perf 就上场了。它能采样 CPU 的调用栈,告诉你热点在哪。
# 采样 10 秒,记录 CPU 周期事件
perf record -e cycles -a -g -- sleep 10
perf report --stdio
嗯,这里要注意:perf 需要 root 权限或者 perf_event_paranoid 调低。我踩过坑,在容器里忘了加 --privileged,结果 perf 啥都采不到。
再说 strace。这工具我一般不用在长期监控,而是「抓现场」。比如推理请求突然变慢,我会 strace 一下 worker 进程,看看是不是卡在某个系统调用上。
strace -p <PID> -c -S time # 按耗时排序系统调用
-e trace=network 缩小范围。曾经有一次 strace 了 30 秒,结果进程超时雪崩……
2.2 Python 性能分析工具:函数级显微镜
系统工具告诉你「哪个进程有问题」,但 Python 项目里,你还得知道「哪个函数有问题」。cProfile 是 Python 自带的,零依赖,我几乎每个项目都会跑一轮。
python -m cProfile -o output.prof my_inference.py
# 然后用 pstats 或 snakeviz 可视化
python -m snakeviz output.prof
但 cProfile 有个毛病——它只统计函数调用次数和总耗时,不告诉你每一行代码的耗时。所以遇到「一个函数很慢,但不知道哪一行慢」的情况,我会用 line_profiler。
# 在目标函数上加 @profile 装饰器
kernprof -l -v my_inference.py
它会输出每一行的执行次数和耗时百分比。我记得有一次优化一个图像预处理函数,用 line_profiler 发现 cv2.resize 占了 70% 的时间,换成 torchvision.transforms 后直接提速 3 倍。
内存方面,memory_profiler 是我的常用工具。尤其是推理服务显存泄漏的时候,用它画个内存曲线,一目了然。
python -m memory_profiler my_inference.py
cProfile 做全局扫描,line_profiler 深挖热点函数,memory_profiler 盯住内存。三件套下来,90% 的 Python 性能问题都能定位。
2.3 深度学习框架内置工具:算子级透视
到了模型推理层面,Python 函数分析就不够用了。你得知道每个算子花了多少时间、显存怎么分配的。PyTorch Profiler 和 TensorFlow Profiler 就是干这个的。
先说 PyTorch Profiler。我习惯用 torch.profiler 的 context manager 模式,灵活且开销低。
import torch.profiler as profiler
with profiler.profile(
activities=[profiler.ProfilerActivity.CPU, profiler.ProfilerActivity.CUDA],
record_shapes=True,
with_stack=True
) as prof:
output = model(input_tensor)
print(prof.key_averages().table(sort_by="cuda_time_total", row_limit=20))
输出里你会看到每个算子的 CUDA 耗时、调用次数、输入形状。我经常用这个来发现「意料之外的慢算子」。比如有一次,一个 LayerNorm 居然占了 15% 的 GPU 时间,换成 torch.nn.functional.layer_norm 的 fused 版本后降到了 3%。
TensorFlow Profiler 类似,但通常配合 TensorBoard 使用。我个人觉得它的 trace viewer 更直观,能看到 timeline 上每个 op 的起止时间。
# 在 TensorFlow 2.x 中启用 profiler
tf.profiler.experimental.start('logdir')
# 运行推理步骤
tf.profiler.experimental.stop()
然后打开 TensorBoard,选择「Profile」标签,就能看到 step 耗时、输入 pipeline 瓶颈、TPU/GPU 利用率等等。
perf 或 strace 下钻到系统层。这样从模型到内核,一层层剥开,不会漏掉任何瓶颈。
2.4 工具链选择速查表
| 场景 | 推荐工具 | 主要作用 |
|---|---|---|
| CPU / 内存整体监控 | top, htop | 进程级资源占用 |
| CPU 热点 & 调用栈 | perf | 采样分析内核/用户态热点 |
| 系统调用耗时 | strace | 追踪文件/网络/锁等系统调用 |
| Python 函数耗时 | cProfile | 函数调用次数与总耗时 |
| Python 行级耗时 | line_profiler | 逐行代码执行时间 |
| Python 内存使用 | memory_profiler | 内存占用曲线与泄漏定位 |
| PyTorch 算子分析 | torch.profiler | 算子耗时、显存、输入形状 |
| TensorFlow 算子分析 | tf.profiler | step 耗时、op trace、TPU 分析 |
这张表我打印出来贴在工位上。你想想看,遇到性能问题,先对号入座,选对工具,比瞎猜高效一百倍。
一句话总结: 系统工具看全局,Python 工具看函数,框架工具看算子。三者配合,没有定位不了的性能瓶颈。