P-state 与 C-state:两本独立的账
系列前文讲调度器与上下文切换成本时,都默认 CPU 在满频运转;这一篇专门补上「CPU 跑多快、睡多深」这个变量。功耗治理的两大杠杆常被混为一谈,先把账本分开:P-state 管跑多快——它是频率与电压的档位组合,DVFS(动态电压频率调节)的产物,频率越高电压也得跟着抬,功耗大致随电压的平方增长,所以降频省电的效果是超线性的;C-state 管睡多深——空闲时关时钟、断部分供电的休眠档,C0 表示在跑,C1 浅睡(时钟关了,唤醒很快),C6 一类深睡(整个核断电、状态存到别处,唤醒慢),睡得越深越省电,但唤醒延迟越长、缓存冷却越狠。
两本账由两个子系统分管:P-state 归 cpufreq,C-state 归 cpuidle。调度器眼里它们是同一枚硬币的两面:负载高时升频,负载空时降频加深睡。但它们对延迟的影响方向相反——升频降延迟,深睡加延迟。很多「性能调优」只调了 P-state 就收工,结果尾延迟毛刺依旧,病根其实在 C-state,这一篇后半会专门算这笔账。
两本账还有一个时间尺度的差别:P-state 的切换是微秒到毫秒级(硬件托管的 HWP 甚至更快),C-state 的进出也是微秒量级,但深睡的「隐性成本」(缓存重建、 TLB 降温)远超表面数字。理解切换成本本身,才能理解后文 governor 与 idle 策略为什么那样设计。
P-state 账本上再记一笔现代现象:睿频。额定频率之上,CPU 在功耗温度预算内还能短暂冲到更高档;重负载指令形态(宽向量、高吞吐乘加)会触发降档——同一份代码在不同状态下频率差出三成是常态。「看频率」永远要看区间与驻留时间,而不是一个数字;做性能基线时要把温度与负载形态一起锁住,否则对比数据全部失真。
cpufreq 框架与 governor 全集
cpufreq 以 policy 为单位管理——共享同一组调频档位的物理核心组成一个 policy(超线程兄弟核通常绑在一起)。sysfs 暴露在 /sys/devices/system/cpu/cpuN/cpufreq/ 下:scaling_cur_freq 看当前频率,scaling_available_governors 列可用策略,scaling_governor 一行切换,scaling_min_freq 与 scaling_max_freq 限定上下限。用户态工具 cpupower frequency-info 一条命令汇总全部信息,cpupower frequency-set -g performance 全局切策略,是最常用的两板斧。
governor 是「谁来决定频率」的策略器,全家福如下。performance 锁最高频,功耗换延迟;powersave 锁最低档(在 intel_pstate 主动模式里语义不同,见下节);userspace 让用户态守护进程直接写频率,是老式笔记本调频工具的遗物;ondemand 与 conservative 是采样式策略——周期性读负载,ondemand 超阈值猛拉到最高、负载下降再回落,conservative 渐进升降更平缓;schedutil 是现代内核的推荐默认:它直接读调度器的利用率信号(每个任务组的负载追踪数据),调度器决定升频、空闲路径决定降频,省掉了独立采样线程,响应最快、行为与调度决策天然一致。ARM 平台基本是 schedutil 的天下,x86 上则要看驱动是谁。
选 governor 的朴素逻辑:延迟敏感服务锁 performance; desktop 与电池设备用调度器协同的 schedutil 或传统 ondemand;批处理且电费敏感的场景可以降频上限(scaling_max_freq)硬顶——「锁最高频」与「硬顶最高频」是两个不同动作,前者改策略,后者改档位上限,混用会得到各种诡异表现。
采样式策略的可调参数顺带点名:ondemand 一族在 sysfs 里暴露采样周期与升降频阈值,当年笔记本调优靠它们精修;schedutil 的行为基本交给调度器信号,只保留速率限制类小旋钮。两条路线的哲学差异可以概括成一句话:采样式策略是周期性地看,schedutil 是决策时顺便调——后者把频率决策与调度决策放进同一个时钟域,消除了两类决策互相打架的可能。
调频与调度器的协同还有一个近年看点:调度器开始感知能耗(能量感知调度),按各核的能耗模型把任务放到「每瓦性能最好」的核上——大小核移动平台是主战场,服务器平台上意义渐显。频率治理与放置治理合流是大方向:以前调频管「多快」、调度管「在哪跑」,现在两边开始共享同一份性能能耗模型。运维暂时不需要为此做额外动作,但读懂这份趋势,再看到内核配置里成组的能耗模型条目就不会陌生。
intel_pstate、HWP 与 acpi-cpufreq
x86 上有两代调频驱动的路线之争。acpi-cpufreq 是通用路线:内核按 ACPI 表暴露的档位自己算频率,软件全权决定。intel_pstate 是 Intel 专属路线,分主动与被动两模式:主动模式里内核只提交一个「意向百分比」,实际频率由 CPU 硬件(HWP,Hardware P-states)自主微调——CPU 比内核更懂自己当下的功耗、温度与电流余量,微秒级的硬件反馈环远快于软件采样;被动模式则退化成类 acpi-cpufreq 的软件调频,供需要确定性控制或老硬件的场景。判断自己在哪条路上:看 cpufreq 目录下的 scaling_driver 文件内容,或 cpupower frequency-info 的 driver 字段。
主动模式下 scaling_governor 只剩 performance 与 powersave 两个可选,且 powersave 并不是「锁最低频」,而是「把意向交给硬件自调」——很多初见者被这个名字骗到,误以为自己跑在低频上,实际负载一来频率立刻上去。HWP 还有更细的旋钮 EPP(energy_performance_preference,性能与省电的倾向滑杆),取值如 performance、balance_performance、balance_power、power,cat /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_preference 即可查看,tuned 一类工具会顺手把它一起配了。
这套演进的总结论:新硬件上「内核全权调频」已是过去时,硬件托管加内核提意向是现在时。对运维的直接影响是「看频率」的习惯要改——scaling_cur_freq 看到的是瞬时档位,HWP 机器上它本来就波动;判断策略是否生效要看 policy 与 EPP,而不是盯单一读数。
两条路线的切换也是运维可能遇到的现实问题:内核启动参数可以把 intel_pstate 调成被动模式或整体停用,停用后落回 acpi-cpufreq,governor 集合随之变多。切换的动机通常是「要确定性」——老式基准要求软件全权调频、某些虚拟化环境硬件反馈环失真。HWP 机器上主动模式在多数业务负载里更快也更省电,先测再切,别为情怀退回软件调频。云主机则更特殊,下一节单独说。
数据库与云主机的 performance 惯例
数据库主机的惯例几乎一律是 performance governor。原因不是峰值吞吐(现代调频对持续满载几乎没有差别),而是频率爬坡的延迟:负载从空到满的瞬间,采样式策略需要采样周期加 ramp 时间才升到高频,这段爬坡正好落在「查询突然变多」的头几百毫秒里,P99 与 P999 尾延迟直接被污染。对 OLTP 数据库、缓存服务、网关这类延迟敏感服务,锁高频是花电费买稳定延迟的买卖,业界默认算的是划算。顺带一提,这个惯例写进了大量 DBA 手册与发行版的企业调优指南,属于「不写出来大家也默认做」的部落知识。
实施路径按规模分层:单机用 cpupower frequency-set -g performance;规模化用 tuned——RHEL 系发行版的 tuned-adm profile throughput-performance 或 latency-performance,把 governor、EPP、sysctl、调度器参数一揽子配齐,主机重启后依然生效;容器编排环境则常在节点镜像的 systemd 服务里固化同样的配置。验证别省:cpupower frequency-info 里看 policy 行,压测时反复 cat scaling_cur_freq 观察是否贴着上限跑。
云主机是特例:公有云多数机型把调频决策留在宿主机,guest 里 cpupower 可能报「无法调频」或只剩名义档位——物理机惯例不能照搬,先确认 guest 是否真有调频权。好消息是云厂商对延迟敏感机型普遍默认最高频;风险在「闲时降频」的弹性机型上,账单友好但 P99 不友好,采购规格时值得问一句频率策略。裸金属云机型则与物理机完全同权,惯例原样适用。
一个验证惯例价值的实验形态:把 governor 在 performance 与调度协同策略之间切换,跑同一组 OLTP 型压测,对比的不是平均吞吐(往往接近),而是延迟直方图的右尾——锁频的收益集中在分位数的尾部那一段。多数团队做完第一次对比就把 performance 写进了基线;也有团队发现自己的服务是纯吞吐型(离线计算、批处理),锁频收益为零还多耗电,反过来退回调度协同——惯例的适用前提是延迟敏感,脱离前提套惯例就是浪费。
C-state 深睡与尾延迟
C-state 的账要单独算。C1 一类浅睡唤醒约微秒级,C6 一类深睡唤醒几十微秒起步,还要付一笔隐性成本:睡久了 L1/L2 里的热数据被逐出、分支预测器降温,唤醒后先补缓存才恢复全速。对吞吐型服务这些无所谓,对每秒几十万次事件、要求微秒级响应的服务(低延迟交易、DPDK 类用户态网络栈、实时控制),深睡是毒药——每次「第一包到达」都要多付一次唤醒税,尾延迟的毛刺往往不是网络抖动,而是 CPU 刚睡醒。这类服务出现「定时器回调慢了几十微秒」「收包间隔抖动」的告警时,第一嫌疑人是深睡。
观测与干预工具依次是:powertop 看各 C-state 的驻留分布(进入深睡的占比一目了然);turbostat 看逐核的驻留统计列,压测对比时特别好用。要压住深睡,经典手段是让一个常驻进程打开 /dev/cpu_dma_latency 并写入 0——这等于向内核登记「最大可接受唤醒延迟为零」,空闲路径随即避开深 C-state,进程活着约束就在,进程退出自动恢复;再细一点可以调 EPP 或 per-CPU 的 idle 倾向。极端实时场景还有 idle=poll 启动参数:空闲时干脆空转不睡,功耗换延迟的最后手段,一般只在个别专用节点上用。
最后把两本账合起来:governor 管「跑多快」,idle 策略管「睡多深」,延迟敏感主机的完整清单是 performance governor 加压住深睡,缺一都有毛刺。而吞吐与电费敏感的主机,两者的收益方向一致(降频省电、深睡省电),照发行版默认即可。同一台机器上两类服务混部时,先分清楚谁对毛刺敏感,再决定动哪个旋钮。
一次唤醒的成本构成
把深睡唤醒的账拆开算,能看清毛刺从哪来。第一笔是纯恢复延迟:时钟重启、供电恢复、状态读回,微秒到几十微秒,取决于睡多深;第二笔是缓存重建:睡久了 L1 与 L2 大量逐出,唤醒后头几微秒的访存全是 miss,实际工作恢复全速还要再等一段;第三笔是调度排队:唤醒的进程要先被调度器挑中,若多个事件同时到,排队延迟会盖过前两笔。尾延迟毛刺常常是三笔叠加的结果,只盯第一笔会得出「深睡影响不大」的错误结论——这正是低延迟网络栈干脆全核空转、连浅睡都不要的原因。
干预手段还有参数层:idle 驱动支持用启动参数限制最深的 C-state,效果比运行时约束更彻底也更粗糙——全局一刀切,无法按进程区分;sysfs 的 cpuidle 目录能看到各空闲状态的延迟与驻留统计,是 powertop 之外的第二观测点。常年压深睡用参数,临时压深睡(比如压测窗口)用运行时约束,两类手段按时间尺度选。
把两本账的监控也各配各的:频率侧盯当前频率的驻留分布与 EPP 配置漂移(调优工具与手工配置打架是真实事故来源),idle 侧盯驻留统计里的深睡占比。两类指标都要进基线告警——频率被改回默认、EPP 被别的工具覆盖,这类「配置漂移」比一次性配错更常见,因为能改它们的工具不止一个,各自都觉得自己是好意。
小结
P-state 与 C-state 是功耗治理的两本独立账本:前者决定 CPU 跑多快,由 cpufreq 框架与 governor 治理;后者决定睡多深,由 cpuidle 治理。混谈两者是调优时最常见的概念错误,「锁了 performance 还有毛刺」的谜题多半是 C-state 答的。
governor 的选择逻辑一句话:延迟敏感锁 performance,通用负载交给 schedutil 与调度器协同,电费敏感硬顶上限。x86 新平台调频权已大幅下沉到硬件(HWP 加 EPP),运维要看的从「当前频率」变成「策略与倾向」是否配对。
数据库与低延迟服务锁 performance 是买延迟确定性,不是买吞吐;云主机先确认调频权归属再套物理机惯例。C-state 那本账用 powertop 与 turbostat 观测,用 cpu_dma_latency 一类约束干预,尾延迟毛刺里「CPU 刚睡醒」是高频病因。
最后提醒一个组织层面的经验:频率与 idle 配置属于「改一次管很多年」的基础设施参数,最适合写进节点初始化基线与镜像构建流程,而不是逐机手敲命令。把 governor、EPP、idle 约束、tuned 配置收敛成一份声明式配置,新机器上线自动落位——功耗治理的正确归宿是基线管理,不是运维手艺。
下一篇转向故障语境:内核自己崩溃了怎么办——oops 与 panic 的区别、kexec 的快速重启、kdump 预留现场与 crash 工具的尸检入门。
读者留言
COMMENTS 暂无还没有留言,来说第一句?