用 2 个可运行实验,学会性能分析的"工程思路"
"我怎么用 perf 把问题抓出来"*
一、为什么运维一定要会 perf?
线上慢的时候,常见对话是:
- 「是不是网络慢?」
- 「是不是磁盘不行?」
- 「CPU 不是才 30% 吗?」
- 「感觉哪里不对,但说不清」
👉 perf 的价值只有一句话:
把"感觉慢",变成"函数级证据"。
实验一:用 perf 看懂 CPU / IO / 网络问题
这是 perf 的地基实验,只解决一件事:
这台机器,到底慢在 CPU、IO 还是网络?
实验一整体设计(你先看懂)
我们用一个 Python 程序,人为制造三类问题:
| 模块 | 人为制造的问题 | perf 里能看到 |
|---|---|---|
| CPU | 纯计算死循环 | Python 函数热点 |
| IO | 频繁写文件 + fsync | write / fsync |
| 网络 | socket 收发 | tcp_sendmsg / recv |
程序结构很简单:
main
├── cpu_task() # CPU 热点
├── io_task() # IO 阻塞
└── network_task() # 网络 syscall
实验一代码(直接可运行)
保存为 perf_demo.py
python
import threading
import socket
import time
import os
FILE_PATH = "/tmp/perf_io_test.log"
SERVER_ADDR = ("127.0.0.1", 9000)
# ---------------- CPU 密集 ----------------
def cpu_task():
x = 0
while True:
for i in range(10000):
x += i * i
# ---------------- IO 密集 ----------------
def io_task():
with open(FILE_PATH, "a") as f:
while True:
f.write("hello perf\n")
f.flush()
os.fsync(f.fileno())
time.sleep(0.001)
# ---------------- 网络服务端 ----------------
def network_server():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(SERVER_ADDR)
s.listen(5)
while True:
conn, _ = s.accept()
while True:
data = conn.recv(1024)
if not data:
break
conn.sendall(data)
# ---------------- 网络客户端 ----------------
def network_client():
time.sleep(1)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(SERVER_ADDR)
while True:
s.sendall(b"x" * 1024)
s.recv(1024)
if __name__ == "__main__":
threading.Thread(target=cpu_task, daemon=True).start()
threading.Thread(target=io_task, daemon=True).start()
threading.Thread(target=network_server, daemon=True).start()
threading.Thread(target=network_client, daemon=True).start()
while True:
time.sleep(10)
运行:
bash
python3 perf_demo.py
第一步:top ------ 确认"值得 perf"
bash
top
你会看到:
- python3 占用明显 CPU
- load 有波动
👉 这一步不是分析,只是确认:机器真的在忙
第二步:perf stat ------ 定性判断瓶颈
bash
perf stat -p $(pgrep -f perf_demo.py) sleep 10
重点看这几个:
| 指标 | 怎么理解 |
|---|---|
| cycles | CPU 工作量 |
| instructions | 干了多少事 |
| IPC | 每周期干几条指令 |
| cache-misses | 是否 cache 问题 |
运维级判断法则:
- IPC < 1 → IO / syscall / 阻塞多
- cycles 高但 instructions 不高 → 内核调用多
👉 这是"方向判断",不是结论
第三步:perf top ------ 实时热点(最直观)
bash
perf top -p $(pgrep -f perf_demo.py)
你大概率会看到:
python3 cpu_task
libc.so write
vmlinux tcp_sendmsg
vmlinux recvfrom
👉 到这一步你已经知道:
- CPU 在算什么
- IO 在干嘛
- 网络是不是在内核里忙
第四步:perf record + report ------ 固化证据
bash
perf record -F 99 -p $(pgrep -f perf_demo.py) -g -- sleep 30
perf report
看 report 的核心原则:
- 看 Children,不是 Self
- 排名前几的,就是问题来源
你会看到三类热点:
| 现象 | 含义 |
|---|---|
| cpu_task | CPU 计算热点 |
| write / fsync | IO 阻塞 |
| tcp_sendmsg | 网络 syscall |
第五步:火焰图(强烈推荐)
bash
perf script > out.perf
stackcollapse-perf.pl out.perf > out.folded
flamegraph.pl out.folded > flame.svg
怎么看火焰图(记住 3 条):
- 横向越宽,时间越多
- 最上面是"真正在跑的代码"
- 内核在下面,应用在上面
你会清楚看到三座"山":
- CPU 计算山
- IO 写文件山
- 网络 syscall 山
实验一你应该学会什么?
✅ 不再凭感觉判断瓶颈
✅ 知道 CPU / IO / 网络在 perf 里的"长相"
✅ 能给出一句像样的结论:
perf 显示主要时间消耗在 cpu_task 计算及 write/fsync 系统调用,属于 CPU + IO 混合型负载。
实验二:锁竞争 + 网络慢的进阶分析
这个实验解决 线上最难判断的两类问题:
CPU 不高,但系统很慢
实验二·场景一:制造锁竞争
在原程序中加入锁竞争
python
lock = threading.Lock()
def lock_task():
while True:
with lock:
time.sleep(0.002)
for _ in range(4):
threading.Thread(target=lock_task, daemon=True).start()
perf 里怎么看锁?
bash
perf top -p $(pgrep -f perf_demo.py)
你会看到:
__pthread_mutex_lock
futex_wait
schedule
再采样:
bash
perf record -F 99 -p $(pgrep -f perf_demo.py) -g -- sleep 20
perf report
看到 futex = 锁竞争(90% 没跑)
火焰图里的锁竞争特征
futex_wait
└── __pthread_mutex_lock
└── python threading
👉 山不高,但很宽
👉 时间花在"等锁"
实验二·场景二:tcpdump + perf 分析网络慢
先用 tcpdump 看"包有没有问题"
bash
tcpdump -i lo -nn -tttt tcp port 9000
关注:
- 是否重传
- ACK 是否延迟
- Window 是否变小
再用 perf 看"慢在谁身上"
bash
perf record -e tcp:tcp_sendmsg,tcp:tcp_recvmsg,net:netif_receive_skb \
-a -g -- sleep 20
perf report
判断逻辑:
| 现象 | 结论 |
|---|---|
| tcp_recvmsg 高 | 应用处理慢 |
| netif_receive_skb 高 | 网络/软中断 |
| futex + schedule | 应用被锁住 |
运维工程师级联合结论示例
tcpdump 未发现明显重传,RTT 稳定;
perf 显示大量时间消耗在 tcp_recvmsg 与 futex 等待,说明网络本身正常,瓶颈在应用处理和锁竞争。
最重要的一段(请记住)
perf 不给答案,它给证据链
标准分析路径只有一条:
top
↓
perf stat(定性)
↓
perf top(热点)
↓
perf record(证据)
↓
火焰图(全局)
↓
结合代码下结论
给小白运维的一句话总结
tcpdump 证明"包怎么走"
perf 证明"时间花在哪"
火焰图让争论闭嘴