应先关闭 top 自动排序并盯住 %cpu 数值变化,结合 vmstat 的 r/b 列判断真实瓶颈;用 perf record -g -k 1 采集含内核栈的火焰图,配合 --no-children 分析调用深度;避免盲目调整调度参数,优先使用 chrt 设置实时策略。

top 和 htop 看不出真实瓶颈?先关掉自动排序再盯 %CPU
默认 top 按 %CPU 实时排序,容易让你只看到“最忙的进程”,却漏掉短时爆发、线程抖动或 I/O 等待伪装成 CPU 占用的问题。比如一个进程 %CPU 显示 95%,但实际是频繁阻塞在磁盘读写,top 的 CPU 列只是“就绪态等待时间”的统计偏差。
- 运行
top后按Shift+P确保按 CPU 排序,再按1切换到所有 CPU 核心视图,观察是否集中在单核(说明是串行瓶颈,不是整体算力不足) - 按
t关闭任务视图顶部的“CPU usage”条形图——它会干扰对数字本身变化趋势的判断 - 更可靠的做法:用
htop(需安装),按F4过滤R(Running)状态进程,排除大量S(Sleeping)进程干扰 - 注意
%CPU是采样周期内占用时间占比,如果进程刚启动或刚唤醒,数值可能跳变剧烈,连续观察 5 秒以上再下判断
perf record -g 能抓到函数级热点,但默认不包含内核栈
直接跑 perf record -g -p <pid></pid> 只能拿到用户态调用栈,而很多 CPU 瓶颈其实在内核路径里,比如锁竞争、页回收、ext4 元数据操作。不带内核符号的话,你看到的可能是 [unknown] 或一堆 __x64_sys_* ,完全没法定位。
- 确保已安装对应内核版本的 debuginfo 包,例如 CentOS/RHEL 上运行:
yum debuginfo-install kernel-$(uname -r) - 采集时加
-k 1强制包含内核栈:perf record -g -k 1 -p <pid> sleep 10</pid> - 报告时用
perf report --no-children避免被“折叠优化”隐藏真实调用深度;若看到大量do_syscall_64→ksys_read→ext4_file_read_iter,说明是小文件读性能问题,不是应用逻辑慢 - 注意 perf 默认采样频率是 4000Hz,高负载下可能丢样本,可临时提至
-F 8000,但别超过 10000,否则自身开销反成干扰
vmstat 1 的 r 和 b 列比 %CPU 更早暴露调度瓶颈
vmstat 1 输出里 r(运行队列长度)持续 > CPU 核数,或 b(不可中断睡眠进程数)突增,往往意味着 CPU 不是真忙,而是被 I/O 或锁卡住。这时候杀高 CPU 进程没用,反而可能让队列更乱。
-
r值长期大于$(nproc),说明就绪态任务积压,典型如大量短生命周期线程反复创建销毁(比如 Python 的concurrent.futures.ThreadPoolExecutor配置过小 + 任务粒度太细) -
b> 0 且波动大,大概率有进程卡在D状态(uninterruptible sleep),常见于 NFS 挂载超时、坏盘响应延迟、或某些驱动 bug;用ps aux | awk '$8 ~ /D/ {print}'快速揪出 - 配合
cat /proc/<pid>/stack</pid>查看 D 状态进程的内核栈,比猜更准;若看到wait_on_page_bit_common,优先查内存或存储子系统 - 注意
vmstat的us/sy是平均值,无法反映毛刺;如果r高但us低,基本可断定是内核态争抢(如自旋锁、RCU callback 积压)
不要盲目调 /proc/sys/kernel/sched_min_granularity_ns
网上常有人说“调小这个值能让小任务更快获得 CPU”,但实际在多数业务场景下,它只会增加调度器开销,降低缓存局部性,甚至引发更多上下文切换抖动。Linux CFS 调度器已经足够智能,除非你跑的是确定性实时仿真或高频交易网关,否则改它大概率是负优化。
- 默认值通常是
750000(750μs),在 48 核机器上改到300000后,context-switches/sec可能翻倍,而吞吐量不变甚至下降 - 真正该调的是
/proc/sys/kernel/sched_latency_ns—— 它控制调度周期长度,和核心数正相关;CFS 会自动按比例缩放,手动改容易破坏公平性 - 如果你的应用确实对延迟敏感(比如音视频编码线程),优先用
chrt -f 50设为 SCHED_FIFO,比调 granularity 更直接有效 - 改任何
/proc/sys/kernel/sched_*参数前,先用perf stat -e 'sched:sched_switch' -a sleep 10记录基线,避免把“观测扰动”当成“性能提升”
最麻烦的 CPU 瓶颈,往往藏在用户态和内核态交界处——比如一个 epoll_wait 返回后立刻 write 失败重试,循环飞快,但 perf 看不到用户代码热点,因为大部分时间耗在内核 socket 缓冲区锁或 TCP 重传定时器上。这时候得切到 trace-cmd 或 bpftrace 抓事件链,而不是盯着 %CPU 数字较劲。








