第2章 性能分析工具链

做推理性能优化,第一件事不是调参数,而是「看清楚」瓶颈在哪。我见过太多人上来就改 batch size、换量化精度,折腾半天发现瓶颈在磁盘 I/O 或者 Python GIL 上。所以这一章,咱们就把手头的工具捋一遍——Linux 系统级、Python 进程级、深度学习框架级,三层工具链,一个都不能少。

🔧 性能分析工具链三层体系 Linux 系统级 top / htop perf / strace Python 进程级 cProfile / line_profiler memory_profiler 框架内置工具 PyTorch Profiler TensorFlow Profiler 从系统到进程再到框架,逐层下钻 每个层面解决不同粒度的性能问题 CPU/内存/IO 全景 函数级热点 & 内存 算子/显存/耗时

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  # 按耗时排序系统调用
⚠️ 避坑: 生产环境 strace 会拖慢进程,我一般只 attach 几秒钟,或者用 -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 利用率等等。

我的习惯: 先用框架 profiler 跑一次小 batch,看算子耗时分布;如果有异常,再用 perf 或 strace 下钻到系统层。这样从模型到内核,一层层剥开,不会漏掉任何瓶颈。

2.4 工具链选择速查表

场景推荐工具主要作用
CPU / 内存整体监控top, htop进程级资源占用
CPU 热点 & 调用栈perf采样分析内核/用户态热点
系统调用耗时strace追踪文件/网络/锁等系统调用
Python 函数耗时cProfile函数调用次数与总耗时
Python 行级耗时line_profiler逐行代码执行时间
Python 内存使用memory_profiler内存占用曲线与泄漏定位
PyTorch 算子分析torch.profiler算子耗时、显存、输入形状
TensorFlow 算子分析tf.profilerstep 耗时、op trace、TPU 分析

这张表我打印出来贴在工位上。你想想看,遇到性能问题,先对号入座,选对工具,比瞎猜高效一百倍。

一句话总结: 系统工具看全局,Python 工具看函数,框架工具看算子。三者配合,没有定位不了的性能瓶颈。