资讯动态

Linux调度器性能分析实战:perf sched延迟排查与优化指南

发布时间:2026/10/15 5:05:13 来源:尧图企业网站定制
1. 调度器性能分析为什么值得单独拎出来讲做Linux性能优化的人迟早会碰到一类问题系统CPU占用不高、内存也够、IO看起来也不忙但应用的响应就是一阵一阵地卡延迟毛刺特别明显。这种时候你去看top大概率看不出什么名堂因为问题根本不在资源总量上而在任务在CPU之间的迁移、等待和唤醒路径上。perf sched就是专门用来啃这块硬骨头的工具。perf sched是perf工具集里针对调度器子系统的分析命令它能记录一段时间内所有的调度事件——任务切换、唤醒、迁移、阻塞——然后把这些原始事件重放成可读的统计视图。核心子命令包括perf sched record、perf sched latency、perf sched map、perf sched timehist、perf sched replay等。其中latency和timehist是日常排查用得最多的两个前者给你每个任务的调度延迟汇总后者给你逐事件的调度时间线。这篇文章适合谁看如果你是在做服务器性能调优、嵌入式实时性分析、或者单纯想搞清楚我的进程为什么被踢来踢去那这篇内容基本能覆盖你80%的日常需求。如果你连perf都没装过也没关系我会从最基础的环境准备讲起把每个参数为什么这么设、每个数字代表什么含义都掰开说。全文基于我这些年在一线排查调度问题攒下来的经验很多细节是官方文档里不会写的比如-w的宽度到底怎么选、timehist里那个wait time和sch delay到底差在哪、为什么有时候record出来的数据全是乱的。先说一个最容易被忽略的前提perf sched分析的是调度器视角的时间不是应用视角的时间。这两者之间的差距恰恰就是你要找的答案。举个例子一个线程从被唤醒到真正上CPU跑中间可能经历了唤醒延迟、迁移开销、被更高优先级任务抢占等好几个阶段perf sched能把这些阶段拆开给你看。这就是它比top、vmstat这类粗粒度工具强的地方。2. 环境准备与record参数的门道2.1 内核配置与perf安装的坑在动手之前先确认你的内核开了调度事件追踪。大部分发行版默认是开的但有些精简过的内核会裁掉。检查方法很简单# 查看调度相关tracepoint是否存在 ls /sys/kernel/debug/tracing/events/sched/如果能看到sched_switch、sched_wakeup、sched_wakeup_new、sched_process_exit这些目录说明内核支持。如果这个路径不存在先挂载debugfsmount -t debugfs none /sys/kernel/debugperf本身的安装Debian/Ubuntu系是apt install linux-tools-common linux-tools-$(uname -r)RHEL/CentOS系是yum install perf。这里有个经典坑perf的版本必须和内核版本匹配否则record的时候会报Perf event open failed或者干脆记录不到调度事件。我见过有人用5.4的perf去分析6.1的内核结果sched_switch事件一个都抓不到折腾半天以为是权限问题。所以装完先跑一下perf version uname -r两个版本号对不上就换对应的包。还有一个权限问题。perf sched record需要CAP_SYS_ADMIN或者perf_event_paranoid足够低。临时放开# 查看当前值默认可能是2或3 cat /proc/sys/kernel/perf_event_paranoid # 临时设为-1允许所有事件采集 echo -1 /proc/sys/kernel/perf_event_paranoid注意生产环境不要长期把perf_event_paranoid设成-1采集完记得改回去。这个参数放开意味着任何用户都能采集系统级事件有信息泄露风险。2.2 record命令的参数怎么选perf sched record是整个分析流程的入口它负责把调度事件抓下来存成perf.data。最基础的用法perf sched record -- sleep 10这行命令的意思是开始记录调度事件同时运行sleep 1010秒后sleep结束record自动停止。你也可以用-p指定进程、用-a抓全系统、用-C指定CPU。但真正影响分析质量的是下面这几个参数我一个个说。-g抓调用栈这个参数会记录每次调度事件发生时的内核调用栈。好处是你能看到是谁触发了这次调度比如是mutex_lock还是schedule_timeout坏处是数据量会暴涨好几倍。我的经验是第一轮排查先不加-g先用latency定位到可疑任务第二轮再针对那个任务加-g抓栈。一上来就加-gperf.data动辄几百MB分析起来很痛苦。--filter事件过滤如果你只关心某个特定任务可以过滤perf sched record --filter comm myapp -- sleep 10这样只记录myapp相关的调度事件数据量小很多。但要注意过滤之后你就看不到其他任务对myapp的干扰了所以定位阶段不建议用确认阶段再用。-R原始trace输出这个参数让record把原始事件直接打到stdout而不是存文件。适合快速看一眼事件流但不适合做统计分析。采样时长怎么定这是很多人纠结的问题。我的建议是至少覆盖3到5个问题复现周期。比如你的应用每30秒卡一次那就至少录150秒。录太短可能刚好错过毛刺录太长数据又太大。可以先录60秒看看latency的分布如果毛刺没抓到再延长。2.3 一个完整的record示例假设我要分析一个叫worker的进程为什么延迟高我会这样操作# 第一步确认worker的PID pgrep worker # 假设输出 12345 # 第二步抓60秒同时抓调用栈只抓worker相关 perf sched record -g --filter comm worker -- sleep 60 # 第三步先看latency汇总 perf sched latency --sort max # 第四步如果发现某个延迟特别大用timehist看细节 perf sched timehist -p 12345这套流程我用了很多次基本能覆盖大部分场景。下面几节我会把每个分析命令的输出逐列拆开讲。3. latency视图每个任务的调度延迟账本3.1 输出列的含义逐项拆解perf sched latency的输出长这样我简化了一下-------------------------------------------------------------------- Task | Runtime | Switches | Average delay | Maximum delay | Maximum delay at -------------------------------------------------------------------- worker:12345 | 12.345 ms | 150 | 0.082 ms | 15.234 ms | [timestamp] kworker/0:1:678 | 3.210 ms | 80 | 0.015 ms | 0.890 ms | [timestamp] --------------------------------------------------------------------每一列的含义Runtime这个任务在采集窗口内实际占用CPU的总时间。注意这是上CPU跑的时间不包括等待。Switches这个任务被调度上CPU的次数。次数多说明任务频繁被唤醒和切换。Average delay平均调度延迟即任务从可运行状态到真正上CPU的平均等待时间。Maximum delay最大调度延迟这是最关键的指标毛刺就藏在这里。Maximum delay at最大延迟发生的时间戳方便你回去看那个时刻发生了什么。这里要重点解释一下调度延迟sched delay的定义当一个任务被唤醒或者时间片用完重新入队变成可运行状态到它真正被调度器选中上CPU执行这中间的等待时间就是调度延迟。它和唤醒延迟不是一回事——唤醒延迟是从硬件中断触发到任务被标记为可运行调度延迟是从可运行到真正运行。perf sched latency统计的是后者。3.2 怎么读这些数字拿到输出后我的阅读顺序是这样的第一步看Maximum delay排序。加--sort max参数让延迟最大的任务排在最前面。如果某个任务的Maximum delay是几十毫秒甚至上百毫秒而Average delay只有零点几毫秒说明这个任务平时很规律但偶尔被严重阻塞。这种平均值正常、最大值离谱的模式通常意味着优先级反转或者CPU被长时间占用。第二步看Switches和Runtime的比例。如果Switches很高但Runtime很短说明任务被频繁切换可能是锁竞争激烈或者被高优先级任务反复抢占。如果Runtime很长、Switches很少说明任务一旦上CPU就霸占很久可能是它自己不让出CPU。第三步对比同类任务。比如你有10个worker线程其中9个的Maximum delay都在1ms以内只有1个到了20ms那问题大概率出在那个线程绑定的CPU上或者它和其他重负载任务共享了CPU。3.3 一个真实的排查案例之前遇到过一个场景一个数据处理服务平时P99延迟5ms但每隔几分钟会飙到200ms。用perf sched latency --sort max一看Maximum delay 198msAverage delay 0.3ms。时间戳指向某个时刻用timehist回看那个时刻发现是一个kworker在做内存回收kswapd相关把CPU占住了将近200ms。这个案例的教训是调度延迟的毛刺往往不是你的应用自己造成的而是系统级的后台任务。所以分析的时候不要只盯着自己的进程latency输出里那些kworker、migration、rcu_*线程的延迟同样值得关注。实操心得perf sched latency默认只显示有调度事件的任务。如果某个任务完全没出现要么它没被调度过不可能要么record的时候过滤条件把它排除了。检查一下你的--filter是不是写错了。4. timehist视图逐事件的调度时间线4.1 timehist和latency的区别如果说latency是汇总账本那timehist就是流水账。它把每一个调度事件按时间顺序列出来包括任务唤醒、上CPU、下CPU、迁移等。当你从latency定位到某个时间点有问题就需要用timehist去看那个时间点前后到底发生了什么。基础用法perf sched timehist输出大概是这样time cpu task name wait time sch delay run time [tid/pid] (msec) (msec) (msec) --------------- ------ ------------------------------ --------- --------- --------- 1234.567890 [0001] worker[12345] 0.000 0.000 0.500 1234.568390 [0001] kworker/1:2[678] 0.120 0.050 0.200 1234.568590 [0001] worker[12345] 0.200 0.080 0.3004.2 三个时间列的区别这是最容易搞混的地方我重点讲wait time任务从上一次下CPU到这一次被唤醒之间的时间。也就是任务处于睡眠状态多久了。这个值大说明任务在等IO或者等锁。sch delay调度延迟和latency里的定义一样从可运行到真正上CPU的等待。run time这次上CPU跑了多久。理解这三个值的组合能帮你快速判断问题类型wait timesch delayrun time可能的问题大小正常任务在等IO/锁不是调度问题小大正常任务被抢占CPU竞争激烈小小大任务自己霸占CPU需要检查代码大大大系统整体过载需要扩容或优化4.3 用timehist定位毛刺回到上一节那个198ms延迟的案例。我先用latency拿到最大延迟的时间戳然后用timehist加上时间过滤perf sched timehist --time 1234.5,1234.7这样只看1234.5到1234.7这段时间的事件。输出里能清楚看到worker在1234.567被唤醒但直到1234.765才上CPU中间这段时间CPU一直被kworker占着。再结合-g抓的调用栈就能确认是内存回收路径。timehist还有几个实用参数-p pid只看指定进程--state显示任务下CPU时的状态S睡眠、R运行、D不可中断等-w显示唤醒事件能看到是谁唤醒了任务--max-stack配合-g使用限制栈深度注意timehist的输出可能非常长一个繁忙的系统每秒有几千次调度。所以一定要配合--time或者-p过滤否则刷屏刷到怀疑人生。5. map视图与其他辅助命令5.1 mapCPU占用的一目了然视图perf sched map用字符画的方式展示每个CPU在每个时刻跑的是哪个任务。输出类似*A0 A1 . . *B0 . . . . *A0 . . *B0 . . . . .每个字符代表一个任务*表示这个CPU刚发生了上下文切换.表示空闲。这个视图的好处是一眼看出CPU有没有空闲、任务有没有在CPU之间跳来跳去。用法perf sched map # 只看特定时间范围 perf sched map --time 1234.5,1234.6如果发现某个任务在多个CPU之间频繁出现比如A0在CPU0、CPU1、CPU2之间反复横跳说明它没有绑定CPU缓存局部性差性能会受影响。这时候可以考虑用taskset绑定。5.2 replay调度行为的重放perf sched replay会模拟重放采集到的调度事件输出一个如果按这个调度序列重放总耗时是多少的统计。这个命令用得相对少主要用来做调度器的回归测试。日常排查基本用不上但知道有这么个东西。5.3 几个辅助命令perf sched script把调度事件以脚本形式输出方便自己写脚本处理。perf sched trace实时打印调度事件类似trace-cmd。perf sched stats输出调度器的统计信息比如上下文切换总数、迁移次数等。这些命令我平时用得不多但script在需要自定义分析的时候很有用。比如你想统计某个任务被迁移了多少次perf sched script | grep worker | grep migrate | wc -l6. 常见问题与排查速查表6.1 record报错的几种情况问题一perf sched record报Permission denied原因perf_event_paranoid太高或者没有root权限。解决用root跑或者临时调低perf_event_paranoid。问题二record成功但latency输出为空原因大概率是--filter写错了或者采集期间目标进程没有调度事件。检查filter语法确认进程确实在运行。问题三perf.data特别大原因加了-g或者采集时间太长。解决缩短采集时间或者先不加-g。问题四时间戳对不上原因perf用的是单调时钟和date命令的墙上时钟不一样。需要换算的话用perf script --header看采集起始时间。6.2 分析时的常见困惑困惑一Average delay很低但Maximum delay很高正常吗正常但要看高到什么程度。如果Maximum是Average的100倍以上说明有偶发的严重阻塞值得深挖。如果只是几倍可能是正常的调度抖动。困惑二为什么我的任务延迟不高但应用还是卡调度延迟只是延迟的一部分。应用卡可能是IO等待、锁竞争、内存分配等原因。perf sched只能告诉你调度层面的问题其他层面要用perf record、strace等工具配合。困惑三sch delay和wait time总是分不清记住一句话wait time是睡了多久sch delay是醒了多久才轮到。睡是主动的等IO醒着等是被动的被抢占。6.3 避坑清单采集前先确认perf版本和内核版本匹配第一轮排查不要加-g数据量太大--filter只在确认阶段用定位阶段会漏掉干扰信息采集时长至少覆盖3到5个问题周期分析时不要只看自己的进程系统后台任务的调度同样重要timehist一定要加时间或进程过滤否则输出爆炸生产环境采集完记得恢复perf_event_paranoid7. 从数据到结论的完整分析路径7.1 一套可复用的分析流程我把上面这些串成一套流程你照着走就行明确问题先搞清楚是延迟毛刺、吞吐下降还是CPU利用率异常。粗粒度采集perf sched record -- sleep 60不加-g不加filter。看latency汇总perf sched latency --sort max找出Maximum delay最大的几个任务。定位时间点记下最大延迟的时间戳。看timehist细节perf sched timehist --time 起,止看那个时间点前后的事件。看map视图perf sched map --time 起,止确认CPU占用情况。二次采集针对可疑任务加-g和--filter重新采集抓调用栈。结合其他工具用perf record看CPU热点用strace看系统调用交叉验证。7.2 一个完整的分析记录假设我分析一个叫encoder的进程发现它每隔一段时间延迟飙升。按流程走第一步采集60秒perf sched record -- sleep 60第二步看latencyperf sched latency --sort max输出显示encoder的Maximum delay是45msAverage是0.5ms时间戳在1234.5附近。第三步看timehistperf sched timehist --time 1234.4,1234.6发现encoder在1234.45被唤醒但直到1234.495才上CPU中间CPU被kworker/2:1占着。第四步看mapperf sched map --time 1234.4,1234.6确认CPU2在那段时间确实被kworker占满。第五步二次采集抓栈perf sched record -g --filter comm kworker/2:1 -- sleep 10 perf sched timehist -p kworker_pid --max-stack 8看到调用栈里有shrink_node、do_try_to_free_pages确认是内存回收导致的。第六步结论encoder的延迟毛刺是内存回收线程抢占CPU导致的。解决方案可以是调整内存回收策略、给encoder绑核、或者提高它的调度优先级。7.3 参数计算的一个小例子有人问过perf sched latency里的Average delay是怎么算的其实很简单就是所有调度延迟的总和除以切换次数。假设一个任务被切换了100次每次的调度延迟加起来是8ms那Average就是0.08ms。这个值本身意义不大重要的是它的分布。如果100次里有99次是0.01ms1次是8ms那Average会被那一次拉高但真正的问题就是那一次。所以永远优先看Maximum delayAverage只是参考。8. 一些压箱底的经验8.1 关于采集时机的选择调度问题往往和系统负载相关。如果你在系统空闲的时候采集可能什么问题都抓不到。我的做法是在问题复现的时候采集同时记录当时的系统负载。可以用vmstat 1在旁边跑着采集结束后对比时间戳。这样能确认问题是不是和负载高峰相关。8.2 关于多核系统的分析多核系统上调度问题会更复杂因为任务可能在CPU之间迁移。perf sched map在多核场景下特别有用能直观看到迁移模式。如果发现任务频繁迁移可以考虑用taskset绑定CPU调整/proc/sys/kernel/sched_migration_cost_ns增大迁移成本减少迁移检查NUMA配置避免跨NUMA节点迁移8.3 关于实时性要求的场景如果你的场景对延迟极其敏感比如音视频处理、工业控制perf sched能帮你确认是否满足实时性要求。重点关注Maximum delay是否超过你的deadline。如果超了考虑用SCHED_FIFO或SCHED_RR实时调度策略用isolcpus隔离CPU避免其他任务干扰用cgroup限制后台任务的CPU占用8.4 一个容易被忽略的细节perf sched采集的是调度事件但调度事件本身也有开销。在高频调度的系统上采集过程可能会轻微影响系统性能。所以采集时间不要太长够用就行。另外采集的时候尽量避开业务高峰期除非你要分析的就是高峰期的问题。8.5 关于数据保存和对比perf.data文件可以保存下来用perf sched latency -i perf.data离线分析。我习惯把每次采集的数据按日期-场景命名保存比如20240115-encoder-latency.data。这样优化前后可以对比用数据说话。对比的时候重点看Maximum delay的变化如果优化后Maximum从45ms降到5ms那就是实打实的改善。最后分享一个小技巧如果你觉得perf sched的输出太原始可以把它导出成CSV用表格软件画图。perf sched timehist --csv能输出CSV格式然后你就能画延迟随时间变化的曲线毛刺一目了然。这个做法在给团队做汇报的时候特别有用比一堆数字直观多了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑