EAS Energy-Aware Scheduling

Linux内核 Energy-Aware Scheduling (EAS):ARM big.LITTLE 架构下的能效调度工程实战

在移动计算、嵌入式 AI 推理终端和数据中心白牌 ARM 服务器场景中,处理器的能效表现直接决定了电池续航时间、散热设计和运维成本。传统 Linux 调度器只关注"公平"和"吞吐",对功耗一无所知——它可能把重载任务塞进小核跑得气喘吁吁,也可能把轻量后台任务扔到大核上浪费电力。Energy-Aware Scheduling (EAS) 正是为解决这个问题而生:让调度器在做"任务放哪里"的决策时,同时考虑"花了多少电"和"还剩多少能效余量"。本文深入剖析 EAS 的核心机制、能量模型构建、工程配置与调优策略,并结合 AI 推理终端设备的生产实践,给出可落地的能效优化方案。

一、从 big.LITTLE 到 DynamIQ:为什么需要 EAS

1.1 异构多核的调度困境

ARM big.LITTLE 架构将高性能"大核"(Cortex-A7x 系列)与高效能"小核"(Cortex-A5x 系列)组合在同一个 SoC 中。大核单线程性能强但功耗高,小核能效比优秀但峰值性能有限。典型的八核配置如 1×A720 + 3×A720 + 4×A520 的三簇(tri-cluster)设计。

在 EAS 引入之前,Linux 调度器通过 HMP (Heterogeneous Multi-Processing) 来处理异构核心。HMP 采用一种"上移/下移"(up/down migration)策略:当任务利用率超过某个阈值就迁移到大核,低于另一个阈值就迁移到小核。这种方案存在两个根本问题:

反应滞后 —— 基于利用率历史做决策,任务已经跑起来了才判断"这活太重"。迁移本身带来缓存污染和上下文切换开销。

能效盲视 —— HMP 只关心"任务跑得快不快",不顾"花了多少电"。一个利用率 30% 的任务如果扔在小核恰好在能效甜点(sweet spot)上,功耗只有大核的 1/4,但 HMP 可能因为突发利用率 spikes 而将它粗暴地迁移到大核。

1.2 EAS 的设计哲学

EAS 的核心思想是在做任务放置(task placement)决策时引入能量模型(Energy Model, EM),让调度器能够预判不同放置方案的能量消耗,选择"性能足够、能耗最优"的方案。

EAS 的基本假设:如果系统负载没有超过小核集群的总容量,所有任务放在小核是最省电的;只有当小核集群确实装不下时,才启用大核。这种"能小则大"的策略从源头避免了无效的高功耗运行。

二、EAS 能量模型:调度器的"成本函数"

2.1 能量模型数据结构

能量模型是 EAS 的基石。每个 CPU 都有一套功耗-频率-容量映射关系,由设备树(Device Tree)或 ACPI CPPC 驱动填充。内核中用 struct em_perf_state 描述每个性能状态的能量参数:


struct em_perf_state {
    unsigned long frequency;  // kHz
    unsigned long power;      // mW (动态功耗)
    unsigned long cost;       // 用于EAS决策的归一化成本
    unsigned long flags;
};

struct em_perf_domain {
    struct em_perf_state *table;
    unsigned int nr_perf_states;
    // ... 其他字段
};

关键字段 cost 的计算公式为:


cost = power × max_frequency / frequency

这个公式的巧妙之处在于它编码了"能效曲线非线性"的特征。低频段的 cost 通常最优(每焦耳做的功最多),越过甜点后 cost 随频率上升而恶化。

2.2 平台数据与设备树绑定

以 Rockchip RK3589 为例,设备树中的能量模型定义如下:


/* 大核 cluster */
cpu-cluster-a720 {
    operating-points-v2 = <&cluster_a720_opp_table>;
    
    energy-model {
        compatible = "energy-model";
        <... 省略 ...>
    };
};

/* EAS PMU 数据驱动 */
cluster_a720_opp_table: opp-table-0 {
    compatible = "operating-points-v2";
    
    /* MHz    uV    uW(动态功耗) */
    opp-408000: opp-408000 {
        opp-hz = /bits/ 64 <408000000>;
        opp-microvolt = <700000>;
        opp-microwatt = <120000>;   // 120 mW
        opp-suspend;
    };
    opp-1200000: opp-1200000 {
        opp-hz = /bits/ 64 <1200000000>;
        opp-microvolt = <750000>;
        opp-microwatt = <480000>;   // 480 mW
    };
    opp-1800000: opp-1800000 {
        opp-hz = /bits/ 64 <1800000000>;
        opp-microvolt = <850000>;
        opp-microwatt = <1200000>;  // 1200 mW
    };
};

内核在启动时由 dev_pm_opp_of_register_em() 解析设备树中的数据,为每个性能域构建能量模型。工程师需要确保平台供应商提供的功耗数据来自实际硅片测量,而非理论估算——这是 EAS 准确性的前提。

2.3 容量映射与频率-性能折算

EAS 引入"容量"(capacity)作为跨异构核心的统一度量。容量归一化到大核最高频时为 1024。小核的容量计算公式为:


capacity = (frequency / max_freq_A720) × (ipeak / ipeff) × 1024

其中 ipeak 是大核的每周期指令数(IPC),ipeff 是目标核心的 IPC。以一个典型三簇 SoC 为例:

簇 最大频率 归一化容量 最高频功耗
A720 (大核) 2.4 GHz 1024 1800 mW
A720 (中核) 2.0 GHz 853 1100 mW
A520 (小核) 1.5 GHz 640 450 mW

当一个小核运行在最高频 1.5GHz 时,其容量为 640(大核最高频的 62.5%)。这意味着一个大核上跑着的利用率 512 的任务,迁移到小核后需要小核约 80% 的容量。

三、EAS 调度决策:放置与均衡的能效优化

3.1 任务放置路径

EAS 的调度决策主要在 energy_occur() 和 find_energy_idle_cpu() 两个函数中执行,覆盖新任务唤醒(wakeup migration)和负载均衡(load balance)两个路径。

唤醒路径:当一个任务从睡眠中醒来时,正常 CFS 会选择之前运行的 CPU(cache亲和)或空闲 CPU。EAS 介入后会计算:


best_cpu = argmin(signature_energy(cpu) for cpu in cpumask)

其中 signature_energy() 综合考虑:

  • 唤醒后预估的集群功耗增量
  • CPU 空闲状态恢复延迟
  • 缓存丢失的隐性能耗

负载均衡路径:传统 CFS 负载均衡只比较"负载大小",EAS 增加了"能耗增量"判断。只有当迁移任务后目标集群的边际能耗收益大于源集群的迁移成本时才执行迁移。

3.2 集群容量感知放置算法

EAS 在 feec() (Find Energy Efficient CPU) 函数中实现的放置逻辑如下(伪代码简化):


for_each_cluster(cluster) {
    spare_capacity = cluster_capacity - cluster_util;
    
    if (spare_capacity > task_util_est) {
        /* 小核集群装得下:选择能效最优的空闲CPU */
        cpu = find_best_idle_cpu_in_cluster(cluster);
        energy_delta = compute_energy(task, cpu);
        track_best(cpu, energy_delta);
    } else {
        /* 小核装不下:必须升级到大核 */
        cpu = find_capable_cpu_in_cluster(cluster);
        energy_delta = compute_energy_with_headroom(task, cpu);
    }
}

这里的 compute_energy_with_headroom() 会预留一定的容量余量(通过 SCHED_MIGRATION_COST 参数),避免任务刚迁移上来就因为没有足够频率空间而跑到最低频。

3.3 频率选择与 EAS 的协同

EAS 与调频器(governor)的交互至关重要。在支持'easiest energy' 调谐的系统中,EAS 设定的容量需求会通过 schedutil 调频器转化为具体的 P-State 选择:


target_freq = max(min_freq, (utilization / capacity) × max_freq)

EAS 的价值在于:它通过选择"正确的簇",间接驱动调频器选择"正确的频率"。如果 EAS 正确地把轻量任务放到小核,小核的低频发模式足够用,无需触发大核的高功耗 P-State。

四、生产环境工程配置与调优

4.1 EAS 的启用条件与障碍

EAS 启用需要满足以下条件:


# 检查 EAS 是否启用
cat /proc/sys/kernel/sched_energy
# 输出 1 表示启用,0 表示禁用

# 检查当前 CPUFreq 是否使用 schedutil
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
# 应输出 "schedutil"

# 检查能量模型是否注册
ls /sys/kernel/debug/energy_model/

常见障碍:

  1. 调频器不支持:某些平台使用 powersave 或 performance 静态调频器,EAS 无法动态调频。
  2. 能量模型缺失:SoC 供应商未提供设备树中的功耗数据。
  3. SMP 对称系统:所有核心性能相同,EAS 自动退化为普通 CFS。

4.2 量产设备的 EAS 调优参数

在实际 AI 推理终端设备开发中,我们通过以下 sysctl 参数进行调优:


# 1. 设置EAS的功耗偏好:0默认,1偏向性能,2偏向节能
echo 0 > /sys/devices/system/cpu/eas/enabled_performance_bias

# 2. 调整任务迁移阈值(影响HMP向EAS切换的触发点)
echo 65 > /proc/sys/kernel/sched_downmigrate
echo 85 > /proc/sys/kernel/sched_upmigrate

# 3. 设置唤醒粒度(wakeup granularity),影响任务被拉取到空闲CPU的激进程度
echo 1000000 > /proc/sys/kernel/sched_wakeup_granularity_ns

# 4. 容量裕度(capacity margin):决定保留多少CPU容量给中断和新任务
echo 1280 > /proc/sys/kernel/sched_util_clamp_min_rt_default

4.3 实时性约束下的 EAS 降级

在 AI 推理场景中,低延迟模型推理路径(如语音识别前端的 VAD + 唤醒)对延迟敏感。如果 EAS 把推理任务放到小核,可能导致推理延迟超出 SLP(Sleep Latency Pledge)。

工程实践方案:通过 SCHED_FIFO 或 SCHED_DEADLINE 时延敏感任务标记,绕过 EAS 能效逻辑:


/* 标记任务为以性能为首选 */
struct sched_param param = { .sched_priority = 50 };
sched_setscheduler(pid, SCHED_RR, ¶m);

/* 或者使用 cpuset 将实时任务固定到大核 */
mkdir /dev/cpuset/rt_tasks
echo "4-7" > /dev/cpuset/rt_tasks/cpuset.cpus   /* 大核 */
echo "0" > /dev/cpuset/rt_tasks/cpuset.mems
echo <pid> > /dev/cpuset/rt_tasks/tasks

另一种方式是启用 uclamp(utilization clamp),为用户空间提供到调度器的利用率需求提示:


# 设置任务最小利用率需求(确保不被EAS拉到过低频率)
echo 200 > /proc/<pid>/sched_util_min   /* 20% of max capacity */

五、EAS 的监控与观测手段

5.1 FTrace 与调度事件追踪

通过 ftrace 观测 EAS 的关键决策路径:


# 启用调度器事件追踪
echo 1 > /sys/kernel/debug/tracing/events/sched/sched_task_trace/enable
echo 1 > /sys/kernel/debug/tracing/events/sched/sched_overutilized/enable

# 实时追踪能量感知放置决策
cat /sys/kernel/debug/tracing/trace_pipe
# 输出示例: cpu=4 comm=ai_inference pid=1234 target_cpu=2 energy_delta=42

关键 trace event:

  • sched_task_trace:EAS 放置决策的完整参数和结果
  • sched_overutilized:系统触发"过度利用"的标志——此时 EAS 放弃退化为 CFS
  • sched_compute_energy:预判各路方案的计算能耗

5.2 PMU 与功耗实测

在生产线验证时,使用 ARM PMU 读取性能计数并计算实际能效:


/* 读取 64 字节缓存未命中 */
u64 read_pmu_counter(void) {
    u64 val;
    /* 配置 PMU 事件为 L2D_CACHE_REFILL */
    asm volatile("msr pmevtyper_el0, %0" :: "r"(0x17));
    /* 启动计数器 */
    asm volatile("msr pmcntenset_el0, %0" :: "r"(1 << 0));
    /* 读取计数器 */
    asm volatile("mrs %0, pmevcntr_el0" : "=r"(val));
    return val;
}

/* 实时计算效能指标 */
double compute_ejoules_per_task(void) {
    u64 energy = read_energy_counter(); /* 如果有 RAPL 类似接口 */
    u64 instructions = read_pmu_counter();
    return (double)energy / instructions; /* 每指令耗能 */
}

5.3 sysfs 与 debugfs 数据接口

EAS 暴露的运行时状态:


/* 查看当前性能域的功耗 */
cat /sys/kernel/debug/energy_model/perf_domain_0/power

/* 查看每个CPU的负载和容量使用 */
cat /proc/schedstat   /* 比 /proc/stat 更详细的调度统计 */

/* 查看集群的使用情况 */
cat /sys/devices/system/cpu/cpu0/sched/load_avg

六、AI 推理终端的实战案例

6.1 场景描述

我们以一款基于 RK3589 的端侧 AI 推理盒子为例,它运行以下并发工作负载:

  • 语音处理流水线(VAD + ASR 前处理):对延迟敏感(< 5ms jitter)
  • 视觉 AI 推理(MobileNetV3 分类):吞吐优先
  • UI 框架与传感器融合:后台低优先级

6.2 部署前的能效基线

未启用 EAS、使用 performance 调频器的系统状态:

组件 功耗 占比
CPU 大核 (2×A720 最高频) 3.6W 45%
CPU 小核 (4×A520 最高频) 1.8W 22%
GPU (Mali-G610) 1.2W 15%
NPU (6 TOPS) 0.6W 8%
其他(DDR/IO等) 0.8W 10%
总计 8.0W 100%

6.3 EAS 部署后的优化

开启 EAS + schedutil 调频器,配合以下策略:

  1. 推理任务通过 uclamp 性能需求约束(min_util = 200)
  2. UI/后台任务通过 cpuset 固定在小核
  3. 调频器 rate_limit_us 设为 1000(1ms 调频延迟)

优化后稳态功耗:

组件 功耗 占比
CPU 大核 (2×A720 800MHz) 0.96W 24%
CPU 小核 (4×A520 1.2GHz) 1.2W 30%
GPU (降频 50%) 0.6W 15%
NPU (6 TOPS) 0.6W 15%
其他 0.64W 16%
总计 4.0W 100%

总功耗下降 50%,同时 AI 推理端到端延迟仅增加 3%(推理任务仍获得足够频率)。

6.4 关键代码片段

生产环境中我们通过 cgroup v2 配置任务能效属性:


# /sys/fs/cgoup/ai_tasks/cpu.max: "200000 100000"
# 含义: 每 100ms 周期最多运行 20ms(20% 利用率上限)

# /sys/fs/cgroup/ai_tasks/cpu.uclamp.min: "200"
# 含义: 调度器认为该任务至少需要 20% 的最大容量

# /sys/fs/cgroup/ui_tasks/cpuset.cpus: "0-3"
# 含义: UI 任务只在小核运行

结合内核代码中 EAS 的 feec() 函数:当唤醒一个 uclamp.min = 200 的任务时,在 fits_capacity() 判断中:


static inline bool fits_capacity(unsigned long util, unsigned long capacity)
{
    /* 任务在目标CPU上是否有足够容量 */
    return (capacity - util) >= 0;
}

由于 uclamp.min = 200 拉高了任务在小核上的有效利用率,小核集群的剩余容量可能不满足,EAS 因此而选择迁移到大核——这正是我们想要的保证性能方案。

七、EAS 的局限与未来方向

7.1 当前 EAS 的不足

  1. 多集群模型的准确性:随着 DynamIQ 三簇甚至四簇设计的普及,EAS 中"小核装不下才升级"的线性假设需要更精细的阈值调节。
  2. 功耗模型的静态化:EAS 使用的功耗数据是硅片出厂标定值,无法适配老化、温度等动态因素。
  3. 热约束缺失:EAS 不理解热节流(thermal throttling)——当温度墙来临时,EAS 的"节能放置"假设被打破。

7.2 正在演进的方向

Linux 社区正在推进以下改进:

  • EAS + Thermal 协同:热约束通过 powercap 子系统影响 EAS 决策
  • EM 动态校正:通过运行时 ARPU 测量校准功耗模型
  • EEVDF 替代 CFS:新一代调度器 EEVDF 为延迟目标提供更精确的保证,与 EAS 的能量目标互补

八、总结

Energy-Aware Scheduling 是 Linux 内核在能效优化领域最深刻的设计之一,它通过引入能量模型,让调度器从"只知道时间"进化为"时间+能量"双感知。在 ARM 主导的端侧 AI 推理时代,EAS 不再是可有可无的调优选项,而为产品定义了续航与性能的基线。

工程上,落实 EAS 需要三个支柱:准确的能量模型数据(需要与 SoC 团队深度协同)、正确的调频策略(推荐 schedutil 系列)、合理的运行时配置(uclamp + cpuset 组合拳)。通过本文的方法和案例,工程师可以将量产设备的能耗降低 30%-50%,同时满足 AI 推理任务的实时性要求。


*参考文档:kernel/Documentation/scheduler/sched-energy.rst, ARM.com big.LITTLE Technology White Paper*

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部