cpufreq 与 cpuidle

本页目录

低频不等于深度空闲,performance 也不等于禁止 idle。CPUFreq 管活动性能,CPUIdle 管空闲状态,比较时还需区分功率、工作时长与能量⁠。

驱动、属性和默认策略依内核与硬件,以实际 policy 和运行状态为准。

查阅目标阅读位置
性能请求与硬件自治CPUFreq、governor、schedutil、厂商模式
空闲与放置CPUIdle、EAS
证据与比较口径只读检查、能量比较
CPU 电源管理的三个决策层 调度器 任务放到哪个 CPU CPUFreq 活动期间请求什么性能 CPUIdle 没有任务时进入多深空闲 硬件、固件与平台约束 共享电源域、温度、功率预算、唤醒延迟 各层相互影响,但并非由一个算法同时统一选择 CPU、频率和空闲状态。

CPUFreq 的控制单位是 policy

CPUFreq core 管通用接口,governor 根据负载等信息选择性能请求,driver 与具体硬件接口交互。有些驱动可绕过通用 governor,提供自己的控制算法。

多个逻辑 CPU 可能共享性能控制接口,对应同一个 cpufreq_policy。sysfs 的 policyX 是更清楚的观测单位,逐个读取 cpuN/cpufreq 可能重复看到同一策略。CPUFreq

governor 名称要结合驱动解释

通用 governor主要策略不应推导出的结论
performance请求 policy 允许的最高频率永远达到最高实测频率、延迟为零
powersave请求 policy 允许的最低频率整项工作消耗的总能量一定最少
schedutil使用调度器提供的利用率等信号只在 tick 上运行、只看 CFS 单一计数
ondemand根据利用率采样调整在所有场景都比其他策略差
conservative更渐进地调整请求可以忽略驱动转换延迟和工作负载

频率请求受 policy 上下限约束,实际执行还受温度、功率预算与硬件自治影响。scaling_cur_freq 常反映驱动最后请求或架构提供的估计,不一定是读文件瞬间的真实时钟。

schedutil 为什么靠近调度器

任务入队、出队和周期更新等事件会触发利用率更新,不只是每次 scheduler tick。频率不变性信息、容量、RT/DL 负载、I/O wait boost、利用率限制和更新限速等,都可能参与实际路径。

文档中的与利用率成比例公式有助于理解目标:负载越接近容量,请求越高。但它不是可以覆盖整个实现的精确伪代码。驱动支持快速切换时可直接更新,否则可能通过其他执行上下文完成。源码入口是 kernel/sched/cpufreq_schedutil.c。

Intel 与 AMD 的硬件自治模式

intel_pstate 的 active 模式提供自己的 performance/powersave 算法,有 HWP 时还可让硬件在请求范围及偏好下选择性能。这里的 powersave 不等于通用 powersave 固定请求最低频率。passive 模式通常在 scaling_driver 中显示 intel_cpufreq,由通用 governor 发出请求。intel_pstate

amd-pstate 基于 CPPC 性能接口,其能力取决于处理器、固件和配置。passive、active、guided 区别在于软件与硬件怎样分担性能选择;active 常涉及 EPP 偏好,guided 让硬件在给定范围内参与选择。不能只凭“Zen2+”断言每台机器都启用同一模式。amd-pstate

CPUIdle:驻留收益与唤醒代价

空闲 governor 根据预计空闲时长、下一次定时事件和延迟限制等选择驱动提供的 idle state。两个重要参数是退出延迟,以及值得进入该状态的目标驻留时间。

假设某空闲状态目标驻留 100 μs、退出延迟 20 μs:

条件为什么可能不适合进入
预计只空闲 30 μs太短,可能无法获得足够驻留收益
延迟约束最多 5 μs20 μs 退出延迟不满足约束

即使预计空闲很长,也不能忽略唤醒延迟。以上是模型参数,不是某级 C-state 的统一指标。

C1、C6 等名称、实际电源动作与延迟随平台变化。核心和整个 package 的状态也不同:某个核心请求深度空闲,不代表共享电源域一定立即断电。应读驱动暴露的状态与实际驻留统计,而不是背一张固定微秒表。CPUIdle

menu 与 TEO 使用不同的预测与反馈策略。哪一个启用、哪一个效果更好取决于配置和负载,不存在始终更准的默认赢家。

用两个约束筛选候选状态

把预测空闲时长与允许退出延迟放在两个独立轴上,才能解释“空闲很久却仍选浅状态”。下面给出四个人造状态,深度顺序仅用于这个示例。

状态目标驻留 μs退出延迟 μs
poll00
shallow102
deep10020
deeper1000100
states = [("poll", 0, 0), ("shallow", 10, 2),
          ("deep", 100, 20), ("deeper", 1000, 100)]

def choose(predicted_us, latency_limit_us):
    assert predicted_us >= 0 and latency_limit_us >= 0
    allowed = [name for name, residency, latency in states
               if residency <= predicted_us and latency <= latency_limit_us]
    return allowed[-1]

assert choose(30, 50) == "shallow"    # 时间太短,深状态收益不足
assert choose(500, 5) == "shallow"    # 时间足够,但退出延迟超限
assert choose(500, 50) == "deep"
assert choose(100, 20) == "deep"      # 两个边界均满足
assert choose(5000, 0) == "poll"
assert choose(5000, 100) == "deeper"

这段代码只展示约束交集,不是 menu 或 TEO 的实现。真实 governor 还处理状态禁用、历史反馈、定时器与 tick 等条件;异步中断也可能在预测时长之前到来。目标驻留 100 μs 不表示硬件会承诺至少睡满 100 μs,更不能据此延迟处理真实唤醒事件。CPUIdle:目标驻留与退出延迟

分别调整预期空闲时长与允许退出延迟。候选先满足两项门槛,再比较包含进出开销的总能量;最深状态并不天然是任何短空闲窗口的最优选择。

先要求 T 不小于目标驻留、退出延迟不超约束,再比较总能量。C 是本图假设的进出能量开销,不是 Linux governor 的统一评分公式。

正在呈现知识画面
空闲足够长,就一定能进入最深状态吗?

目标驻留时间衡量是否值得进入,退出延迟还必须满足 QoS。很长的预计空闲并不能覆盖严格的唤醒延迟限制。

EAS 是任务放置的能量估计

Energy Aware Scheduling 使用 Energy Model 等信息比较任务放置的能量影响,尤其涉及容量不对称的 CPU。它和利用率估计、性能调节有关,但不是把 cpuidle 与 cpufreq 合并成一次统一决策,也不能简化为总优先选已唤醒 CPU。EAS

在过载、缺少模型或不满足启用条件时,不能假定 EAS 在起作用。跨架构理解时应关注模型与调度条件,而不只把它贴成 ARM 专有开关。

只读检查当前机器

下面使用 Python 遍历已存在的 sysfs 属性,避免不同驱动缺少字段时把命令报错当成故障:

from pathlib import Path

base = Path('/sys/devices/system/cpu')
for policy in sorted((base / 'cpufreq').glob('policy*')):
    print(policy.name)
    for key in ('related_cpus', 'scaling_driver', 'scaling_governor',
                'scaling_min_freq', 'scaling_max_freq', 'scaling_cur_freq',
                'energy_performance_preference'):
        path = policy / key
        if path.exists():
            print(' ', key, path.read_text().strip())
for state in sorted((base / 'cpu0/cpuidle').glob('state*')):
    print(state.name)
    for key in ('name', 'latency', 'residency', 'usage', 'time'):
        path = state / key
        if path.exists():
            print(' ', key, path.read_text().strip())

频率属性通常以 kHz 表示,idle 的 latency/residency/time 相关接口通常以微秒表示;usage 是累计进入次数。统计应比较一段时间的增量,而不是把开机以来累计量当作当前百分比。容器或虚拟机内可见能力可能与物理主机不同。

比较策略时同时看时间与能量

功率是瞬时消耗速率,能量是功率对时间的积分。低功率但工作更久,不一定更省能量;更快完成后进入空闲,也不一定在所有负载都最优。至少比较吞吐或完成时间、尾延迟、能量和温度,并保持任务、背景负载与测量范围一致。

同一个 100 ms 窗口,快完成与低功率的结果可能反转

假设每 100 ms 完成同样一份工作,测量的是同一个电源域,活动与空闲功率在各段内为常数。忽略切换能量,空闲功率统一取 1 W:

方案活动阶段剩余空闲阶段一个周期的总能量
快速完成10 W × 20 ms1 W × 80 ms0.28 J
较低活动功率6 W × 50 ms1 W × 50 ms0.35 J
更高活动能效4 W × 50 ms1 W × 50 ms0.25 J
相同工作、相同周期:活动与空闲都计入能量 快速 10 W × 20 ms 1 W 空闲 0.28 J 较慢 6 W × 50 ms 1 W 空闲 0.35 J 条形长度表示时间;能量 = 各阶段功率 × 时长后相加 100 ms 周期的教学数据,未表示真实 CPU 的频率、功率或状态切换成本。

第二行活动功率更低,但运行更久,周期能量反而更高;第三行进一步降低活动功率后又获胜。因此 race-to-idle 和降频都需要测量支持,不能从策略名称推出答案。如果完成期限是 40 ms,后两行即使能量较低也不满足这个任务的时限。

from fractions import Fraction

def cycle_j(active_w, active_ms, idle_w=1, period_ms=100):
    assert 0 <= active_ms <= period_ms
    return Fraction(active_w * active_ms + idle_w * (period_ms - active_ms), 1000)

fast = cycle_j(10, 20)
slow = cycle_j(6, 50)
efficient = cycle_j(4, 50)
assert (fast, slow, efficient) == (Fraction(28, 100), Fraction(35, 100), Fraction(25, 100))
assert efficient < fast < slow
assert fast / Fraction(100, 1000) == Fraction(28, 10)  # 周期平均功率 2.8 W
assert 20 <= 40 < 50

实际测试还要确认两次完成的工作量相同,并记录频率变化是否真的影响瓶颈。例如等待内存或 I/O 的任务可能无法随核心频率成比例加速;更长空闲也未必能让 package 进入更深状态。存在后台工作时,应同时报告测量域的总能量及基线处理方式,不能把整机背景变化都归因于 governor。

测量范围:电源域能量不等于插座整机功耗

RAPL 等计数器反映特定电源域的能量,不等于插座整机功耗;计数器也会回绕。进一步区分整机睡眠与单设备 runtime PM,见 休眠与唤醒。

累计计数如何换算成区间指标

若同一 idle state 在一个 2 秒观察区间内,time 增加 500000 μs、usage 增加 200,那么软件记账的驻留占比是 25%,每次进入的平均记账时长是 2500 μs。这只是该 CPU、该状态的区间统计;把四个 CPU 的占比直接相加,不会得到单 CPU 意义上的百分比。驱动请求状态的软件统计,也不必等同于硬件 package 实际驻留计数。

能量计数器则先求差,再除以经过时间。对按范围 R 回绕且没有重置的计数器,(after-before) % R 可以求出模 R 的增量;只有已知真实增长小于 R,才可把它当成实际能量差。间隔太长时,即使后一次数值大于前一次,也可能已经回绕过一整圈。

assert 500_000 / 2_000_000 == 0.25
assert 500_000 / 200 == 2500

span_uj, before_uj, after_uj = 1000, 950, 30
observed_delta = (after_uj - before_uj) % span_uj
assert observed_delta == 80
assert (before_uj + 80) % span_uj == after_uj
assert (before_uj + 1080) % span_uj == after_uj  # 同一读数也可能对应多一圈

energy_uj 与 max_energy_range_uj 的单位是 μJ;读取的具体 zone 决定统计范围。父 zone 与子 zone 可能覆盖重叠硬件,不能默认把 package 和其所有子域能量相加作为总量。采样间隔、回绕范围、计数器重置和域名称都应随结果保留。Powercap 接口