English

能耗感知调度

1. 引言

能耗感知调度(Energy Aware Scheduling,简称 EAS)赋予调度器预测其决策对 CPU 能耗影响的能力。EAS 依赖于 CPU 的能耗模型(Energy Model,简称 EM)为每个任务选择能耗高效的 CPU,同时最大程度地减少对吞吐量的影响。本文旨在介绍 EAS 的工作原理、其主要设计决策以及运行 EAS 所需的详细信息。

在深入探讨之前,请注意,在撰写本文时:

/!\ EAS does not support platforms with symmetric CPU topologies /!\

EAS 仅在异构 CPU 拓扑(如 Arm big.LITTLE)上运行,因为这是通过调度节省能耗潜力最大的地方。

EAS 实际使用的 EM _并非_由调度器维护,而是由一个专门的框架维护。有关该框架及其提供内容的详细信息,请参阅其文档(参见设备的能耗模型)。

2. 背景与术语

首先明确:
  • 能量 = [焦耳](像电源设备中的电池一样的资源)

  • 功率 = 能量/时间 = [焦耳/秒] = [瓦特]

EAS 的目标是在完成工作的同时最小化能耗。也就是说,我们希望最大化

performance [inst/s]
--------------------
    power [W]

这等同于最小化

energy [J]
-----------
instruction

同时仍获得“良好”的性能。它本质上是调度器当前仅以性能为目标的替代优化目标。此替代目标同时考虑了能效和性能两个目标。

引入 EM 的目的是让调度器能够评估其决策的影响,而不是盲目应用可能只对某些平台产生积极效果的节能技术。同时,EM 必须尽可能简单,以最大程度地减少对调度器延迟的影响。

简而言之,EAS 改变了 CFS 任务分配给 CPU 的方式。当调度器需要决定任务在哪里运行时(在唤醒期间),EM 被用于在几个好的 CPU 候选者之间做出选择,并挑选出预计能耗最低且不损害系统吞吐量的那个。EAS 所做的预测依赖于平台拓扑的特定知识元素,其中包括 CPU 的“容量”及其各自的能耗成本。

3. 拓扑信息

EAS(以及调度器的其余部分)使用“容量”概念来区分具有不同计算吞吐量的 CPU。CPU 的“容量”表示其在最高频率运行时可以吸收的工作量,与系统中能力最强的 CPU 相比。容量值被归一化到 1024 的范围,并且与通过实体负载跟踪(Per-Entity Load Tracking,PELT)机制计算的任务和 CPU 利用率信号具有可比性。得益于容量和利用率值,EAS 能够估计任务/CPU 的大小/繁忙程度,并在评估性能与能耗权衡时将其考虑在内。CPU 的容量通过 arch-specific 代码通过 arch_scale_cpu_capacity() 回调提供。

EAS 使用的其余平台知识直接从能耗模型(EM)框架中读取。平台的 EM 由系统中每个“性能域”的功耗成本表组成(有关性能域的更多详细信息,请参阅设备的能耗模型)。

调度器在构建或重建调度域时,在拓扑代码中管理对 EM 对象的引用。对于每个根域(rd),调度器维护一个单链表,其中包含与当前 rd->span 相交的所有性能域。列表中的每个节点都包含一个指向 EM 框架提供的struct em_perf_domain的指针。

这些列表附加到根域,以应对独占 cpuset 配置。由于独占 cpuset 的边界不一定与性能域的边界匹配,不同根域的列表可能包含重复元素。

示例 1。

让我们考虑一个有 12 个 CPU 的平台,分为 3 个性能域(pd0、pd4 和 pd8),组织如下:

CPUs:   0 1 2 3 4 5 6 7 8 9 10 11
PDs:   |--pd0--|--pd4--|---pd8---|
RDs:   |----rd1----|-----rd2-----|

现在,假设用户空间决定将系统分为两个独占的 cpuset,从而创建两个独立的根域,每个包含 6 个 CPU。上图中将这两个根域分别标记为 rd1 和 rd2。由于 pd4 与 rd1 和 rd2 都有交集,它将存在于附加到每个根域的链表“->pd”中:

  • rd1->pd: pd0 -> pd4

  • rd2->pd: pd4 -> pd8

请注意,调度器将为 pd4 创建两个重复的列表节点(每个列表一个)。然而,两者都只持有一个指向 EM 框架中相同共享数据结构的指针。

由于对这些列表的访问可能与热插拔和其他操作并发发生,它们像调度器操作的其余拓扑结构一样受到 RCU 的保护。

EAS 还维护一个静态键(sched_energy_present),当至少一个根域满足 EAS 启动的所有条件时,该键被启用。这些条件总结在第 6 节中。

4. 能耗感知任务放置

EAS 覆盖 CFS 任务唤醒平衡代码。它使用平台的 EM 和 PELT 信号在唤醒平衡期间选择能效目标 CPU。当 EAS 启用时,select_task_rq_fair() 调用 find_energy_efficient_cpu() 来进行放置决策。此函数在每个性能域中寻找备用容量最高的 CPU(CPU 容量 - CPU 利用率),因为这是能让我们保持最低频率的 CPU。然后,该函数检查将任务放置在那里是否比将其留在 prev_cpu(即任务上次激活时运行的 CPU)上能节省更多能耗。

find_energy_efficient_cpu() 使用 compute_energy() 来估算如果唤醒任务被迁移,系统将消耗的能耗。compute_energy() 查看 CPU 的当前利用率情况,并进行调整以“模拟”任务迁移。EM 框架提供了 em_pd_energy() API,该 API 计算给定利用率情况下每个性能域的预期能耗。

下面详细介绍一个能耗优化任务放置决策的例子。

示例 2。

让我们考虑一个(虚构的)平台,它有两个独立的性能域,每个域由两个 CPU 组成。CPU0 和 CPU1 是小核 CPU;CPU2 和 CPU3 是大核 CPU。

调度器必须决定将 util_avg = 200 且 prev_cpu = 0 的任务 P 放置在哪里。

CPU 的当前利用率情况如下图所示。CPU 0-3 的 util_avg 分别为 400、100、600 和 500。每个性能域有三个操作性能点(OPP)。与每个 OPP 相关的 CPU 容量和功耗成本列在能耗模型表中。P 的 util_avg 在下图中显示为“PP”。

CPU util.
 1024                 - - - - - - -              Energy Model
                                          +-----------+-------------+
                                          |  Little   |     Big     |
  768                 =============       +-----+-----+------+------+
                                          | Cap | Pwr | Cap  | Pwr  |
                                          +-----+-----+------+------+
  512  ===========    - ##- - - - -       | 170 | 50  | 512  | 400  |
                        ##     ##         | 341 | 150 | 768  | 800  |
  341  -PP - - - -      ##     ##         | 512 | 300 | 1024 | 1700 |
        PP              ##     ##         +-----+-----+------+------+
  170  -## - - - -      ##     ##
        ##     ##       ##     ##
      ------------    -------------
       CPU0   CPU1     CPU2   CPU3

 Current OPP: =====       Other OPP: - - -     util_avg (100 each): ##

find_energy_efficient_cpu() 将首先在两个性能域中寻找备用容量最大的 CPU。在本例中是 CPU1 和 CPU3。然后它将估算如果 P 放置在其中任一 CPU 上,系统的能耗,并检查这是否比将 P 留在 CPU0 上更能节省能耗。EAS 假设 OPP 遵循利用率(这与 schedutil CPUFreq governor 的行为一致,有关此主题的更多详细信息,请参阅第 6 节)。

情况 1. P 迁移到 CPU1:

1024                 - - - - - - -

                                      Energy calculation:
 768                 =============     * CPU0: 200 / 341 * 150 = 88
                                       * CPU1: 300 / 341 * 150 = 131
                                       * CPU2: 600 / 768 * 800 = 625
 512  - - - - - -    - ##- - - - -     * CPU3: 500 / 768 * 800 = 520
                       ##     ##          => total_energy = 1364
 341  ===========      ##     ##
              PP       ##     ##
 170  -## - - PP-      ##     ##
       ##     ##       ##     ##
     ------------    -------------
      CPU0   CPU1     CPU2   CPU3

情况 2. P 迁移到 CPU3:

1024                 - - - - - - -

                                      Energy calculation:
 768                 =============     * CPU0: 200 / 341 * 150 = 88
                                       * CPU1: 100 / 341 * 150 = 43
                              PP       * CPU2: 600 / 768 * 800 = 625
 512  - - - - - -    - ##- - -PP -     * CPU3: 700 / 768 * 800 = 729
                       ##     ##          => total_energy = 1485
 341  ===========      ##     ##
                       ##     ##
 170  -## - - - -      ##     ##
       ##     ##       ##     ##
     ------------    -------------
      CPU0   CPU1     CPU2   CPU3

情况 3. P 留在 prev_cpu / CPU 0 上:

1024                 - - - - - - -

                                      Energy calculation:
 768                 =============     * CPU0: 400 / 512 * 300 = 234
                                       * CPU1: 100 / 512 * 300 = 58
                                       * CPU2: 600 / 768 * 800 = 625
 512  ===========    - ##- - - - -     * CPU3: 500 / 768 * 800 = 520
                       ##     ##          => total_energy = 1437
 341  -PP - - - -      ##     ##
       PP              ##     ##
 170  -## - - - -      ##     ##
       ##     ##       ##     ##
     ------------    -------------
      CPU0   CPU1     CPU2   CPU3

从这些计算来看,情况 1 的总能耗最低。因此,从能效角度来看,CPU 1 是最佳候选。

大核 CPU 通常比小核 CPU 更耗电,因此主要用于小核 CPU 无法满足任务需求时。然而,小核 CPU 并非总是比大核 CPU 更节能。例如,对于某些系统,小核 CPU 的高 OPPs 可能比大核 CPU 的最低 OPPs 能效更低。因此,如果小核 CPU 在特定时间点恰好有足够的利用率,此时唤醒的小任务在 big 端执行可能更能节省能耗,即使它可以在 little 端运行。

即使在大核 CPU 的所有 OPP 都比小核 CPU 能效低的情况下,在特定条件下,将小任务放到大核 CPU 上仍然可能节省能耗。事实上,将任务放到小核 CPU 上可能导致整个性能域的 OPP 升高,这将增加已经在那里运行的任务的成本。如果唤醒任务被放置在大核 CPU 上,它自身的执行成本可能比在小核上运行更高,但它不会影响小核 CPU 的其他任务,这些任务将继续以较低的 OPP 运行。因此,当考虑 CPU 消耗的总能耗时,在大核上运行该任务的额外成本可能小于为所有其他任务提高小核 CPU 的 OPP 的成本。

如果没有系统上所有 CPU 在不同 OPP 下运行的成本知识,上述示例几乎不可能以通用方式且适用于所有平台进行正确处理。得益于其基于 EM 的设计,EAS 应该能够正确应对这些情况而不会遇到太多麻烦。然而,为了确保在高利用率场景下对吞吐量的影响最小,EAS 还实现了另一种机制,称为“过度利用”。

5. 过度利用

从总体来看,EAS 最能发挥作用的用例是那些涉及轻度/中度 CPU 利用率的场景。每当运行长时间的 CPU 密集型任务时,它们将需要所有可用的 CPU 容量,调度器几乎无法在不严重损害吞吐量的情况下节省能耗。为了避免 EAS 损害性能,一旦 CPU 利用率超过其计算容量的 80%,它们就会被标记为“过度利用”。只要根域中没有 CPU 过度利用,负载均衡就会被禁用,并且 EAS 会覆盖唤醒均衡代码。EAS 可能会比其他 CPU 更大地负载系统中最节能的 CPU,如果这可以在不损害吞吐量的情况下完成。因此,负载均衡器被禁用,以防止它破坏 EAS 找到的节能任务放置。当系统未被过度利用时,这样做是安全的,因为低于 80% 的临界点意味着:

  1. 所有 CPU 都有一些空闲时间,因此 EAS 使用的利用率信号可能准确地表示系统中各种任务的“大小”;

  2. 所有任务都应已获得足够的 CPU 容量,无论其 nice 值如何;

  3. 由于存在备用容量,所有任务都必须定期阻塞/休眠,并且唤醒时的均衡就足够了。

一旦一个 CPU 超过 80% 的临界点,上述三个假设中至少有一个变得不正确。在这种情况下,整个根域的“过度利用”标志被设置,EAS 被禁用,并且负载均衡器被重新启用。通过这样做,调度器在 CPU 密集型条件下回退到基于负载的唤醒和负载均衡算法。这能更好地遵守任务的 nice 值。

由于过度利用的概念在很大程度上依赖于检测系统中是否存在空闲时间,因此必须考虑被更高(于 CFS)调度类(以及 IRQ)“窃取”的 CPU 容量。因此,过度利用的检测不仅考虑了 CFS 任务使用的容量,还考虑了其他调度类和 IRQ 使用的容量。

6. EAS 的依赖项和要求

能耗感知调度依赖于系统 CPU 具有特定的硬件属性以及内核的其他功能被启用。本节列出了这些依赖项并提供了如何满足这些条件的提示。

6.1 - 异构 CPU 拓扑

如引言所述,EAS 目前仅支持异构 CPU 拓扑平台。在调度域构建时,通过检查是否存在 SD_ASYM_CPUCAPACITY_FULL 标志来在运行时检查此要求。

有关在 sched_domain 层次结构中设置此标志所需满足的要求,请参阅容量感知调度

请注意,EAS 在根本上与 SMP 并非不兼容,但目前尚未在 SMP 平台上观察到显著的节约。如果将来证明可行,此限制可能会被修改。

6.2 - 能耗模型的存在

EAS 使用平台的 EM 来估算调度决策对能耗的影响。因此,您的平台必须向 EM 框架提供功耗成本表才能使 EAS 启动。为此,请参阅独立 EM 框架的文档设备的能耗模型

另请注意,EM 注册后需要重建调度域才能启动 EAS。

EAS 使用 EM 来对能耗使用做出预测性决策,因此在检查任务放置的可能选项时,它更关注差异。对于 EAS 而言,EM 功率值是以毫瓦特表示还是以“抽象尺度”表示并不重要。

6.3 - 能耗模型的复杂性

EAS 不对 PDs/OPPs/CPUs 的数量施加任何复杂性限制,但将 CPU 数量限制为 EM_MAX_NUM_CPUS,以防止能耗估算期间溢出。

6.4 - Schedutil governor

EAS 尝试预测 CPU 在不久的将来将运行在哪个 OPP 上,以估算其能耗。为此,假设 CPU 的 OPP 遵循其利用率。

尽管在实践中很难提供关于此假设准确性的硬性保证(例如,因为硬件可能不按指令行事),但 schedutil 与其他 CPUFreq governor 不同,它至少_请求_使用利用率信号计算出的频率。因此,唯一支持与 EAS 一起使用的 governor 是 schedutil,因为它是唯一能在频率请求和能耗预测之间提供一定程度一致性的 governor。

不支持将 EAS 与 schedutil 以外的任何其他 governor 一起使用。

6.5 尺度不变利用率信号

为了在所有 CPU 和所有性能状态下进行准确预测,EAS 需要频率不变和 CPU 不变的 PELT 信号。这些可以通过架构定义的 arch_scale{cpu,freq}_capacity() 回调获得。

不支持在未实现这两个回调的平台上使用 EAS。

6.6 多线程 (SMT)

EAS 目前的形式是 SMT 无感知的,无法利用多线程硬件来节省能耗。EAS 将线程视为独立的 CPU,这实际上可能对性能和能耗都适得其反。

不支持在 SMT 上使用 EAS。