能量感知调度
1. 简介
能量感知调度(Energy Aware Scheduling,EAS)赋予调度器预测其决策对 CPU 能量消耗的影响的能力。EAS 依赖于 CPU 的能量模型(Energy Model,EM)来为每个任务选择一个能效高、对吞吐量影响最小的 CPU。本文旨在介绍 EAS 的工作原理、背后的主要设计决策,并详细说明运行 EAS 所需的条件。
在继续之前,请注意:
/!\ EAS 不支持对称 CPU 拓扑的平台 /!\
EAS 仅在异构 CPU 拓扑(例如 Arm big.LITTLE)上运行,因为这是通过调度实现节能潜力最大的地方。
EAS 使用的实际 EM 不 由调度器维护,而是由一个专门的框架维护。有关该框架及其提供的详细信息,请参阅其文档(请参阅设备的能量模型)。
2. 背景和术语
为了从一开始就明确:
能量(energy) = [焦耳](类似于设备上的电池等资源) 功率(power) = 能量/时间 = [焦耳/秒] = [瓦特]
EAS 的目标是最小化能量消耗,同时完成工作。换句话说,我们希望最大化:
performance [inst/s] -------------------- power [W]
这等同于最小化:
energy [J] ----------- instruction
EAS的目标是在保持良好性能的同时,最小化能量消耗。它实质上是调度器当前仅考虑性能的优化目标的替代目标。这种替代目标考虑了两个方面:能效和性能。
引入能量模型的想法是让调度器评估其决策的影响,而不是盲目地应用节能技术,这些技术可能只对某些平台产生积极影响。与此同时,能量模型必须尽可能简单,以最小化调度器的延迟影响。
简而言之,EAS改变了CFS任务分配给CPU的方式。当调度器决定任务应该在哪个CPU上运行时(在唤醒期间),能量模型用于在多个良好的CPU候选项之间进行选择,并选择预测能够在不损害系统吞吐量的情况下产生最佳能量消耗的CPU。EAS所做的预测依赖于对平台拓扑的特定知识元素,其中包括CPU的“容量”及其各自的能量成本。
3. 拓扑信息
EAS(以及调度器的其余部分)使用“容量”概念来区分具有不同计算吞吐量的CPU。CPU的“容量”表示其在最高频率下运行时可以吸收的工作量,与系统中最有能力的CPU相比。容量值在1024范围内进行了归一化,并且可以与由Per-Entity Load Tracking(PELT)机制计算的任务和CPU的利用率信号进行比较。借助容量和利用率值,EAS能够估计任务/CPU的大小/繁忙程度,并在评估性能与能量之间的权衡时考虑这一点。CPU的容量是通过特定于架构的代码通过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,因为这将使我们能够保持频率最低。然后,该函数检查将任务放置在那里是否可以节省能量,与将其保留在prev_cpu上(即任务在其上一次激活中运行的CPU)相比。
find_energy_efficient_cpu()使用compute_energy()来估计如果唤醒的任务被迁移,系统将消耗多少能量。compute_energy()查看CPU的当前利用率情况,并将其调整为“模拟”任务迁移。EM框架提供了em_pd_energy() API,用于计算给定利用率情况下每个性能域的预期能量消耗。
下面详细介绍了一个能量优化的任务放置决策的示例。
- 举例2.
让我们考虑一个(虚构的)平台,由两个独立的性能域组成,每个性能域由两个CPU组成。CPU0和CPU1是小型CPU;CPU2和CPU3是大型CPU。
调度器必须决定将任务P放置在哪个CPU上,其util_avg = 200,prev_cpu = 0。
CPU的当前利用率情况如下图所示。CPU 0-3的util_avg分别为400、100、600和500。每个性能域有三个操作性能点(OPP)。与每个OPP相关联的CPU容量和功耗成本列在能量模型表中。P的util_avg在下图中显示为'PP':
find_energy_efficient_cpu() 首先会查找两个性能域中剩余容量最大的CPU。在这个例子中,是CPU1和CPU3。然后它会估计如果将任务P放置在其中一个CPU上,系统的能量消耗,并检查与将P保留在CPU0上相比,是否能节省一些能量。EAS假设OPPs(操作性能点)遵循利用率(这与schedutil CPUFreq调频器的行为一致,请参阅第6节以获取更多关于此主题的详细信息)。
情况1. P被迁移到CPU1:
情况2. P被迁移到CPU3:
情况3. P保持在prev_cpu / CPU 0:
根据这些计算,情况1具有最低的总能量消耗。因此,从节能的角度来看,CPU 1是最佳选择。
大型CPU通常比小型CPU更耗电,因此主要在任务不适合小型CPU时使用。然而,并非总是小型CPU比大型CPU更节能。对于某些系统,小型CPU的高性能点可能比大型CPU的最低性能点更不节能。例如,如果小型CPU在特定时间点具有足够的利用率,那么在那一刻唤醒的小型任务在大型CPU上执行可能会更节能,即使它适合在小型CPU上运行。
即使在大型CPU的所有性能点都比小型CPU的性能点更不节能的情况下,将小型任务放在大型CPU上可能在特定条件下仍然节省能量。实际上,将任务放在小型CPU上可能会提高整个性能域的性能点,从而增加已在那里运行的任务的成本。如果唤醒任务放在大型CPU上,其自身的执行成本可能会高于在小型CPU上运行时,但这不会影响小型CPU上的其他任务,后者将继续以较低的性能点运行。因此,在考虑CPU消耗的总能量时,将一个任务放在大核心上的额外成本可能小于提高小型CPU上所有其他任务的性能点的成本。
以上示例在通用情况下几乎不可能完全正确,并且对于所有平台而言,也无法得到正确的结果,因为不清楚系统中所有CPU在不同性能点下运行的成本。由于其基于能量模型的设计,EAS应该能够正确处理这些情况而不会出现太多问题。然而,为了确保在高利用率场景下对吞吐量的最小影响,EAS还实现了另一种称为“过度利用”的机制。
5. 过度利用
从一般的角度来看,EAS最有帮助的使用案例是那些涉及轻/中度CPU利用率的情况。每当运行长时间的CPU绑定任务时,它们将需要所有可用的CPU容量,而调度程序在不严重影响吞吐量的情况下无法节省能量。为了避免在EAS中影响性能,一旦CPU的计算容量利用率超过80%,就会将其标记为“过度利用”。只要在根域中没有CPU被过度利用,负载平衡就会被禁用,并且EAS会覆盖唤醒平衡代码。如果可以在不影响吞吐量的情况下,EAS很可能会更多地加载系统中能量效率更高的CPU。因此,为了防止负载平衡破坏EAS找到的节能任务放置方案,负载平衡器被禁用。在系统未过度利用时这样做是安全的,因为低于80%的临界点意味着:
- 所有CPU上都有一些空闲时间,因此EAS使用的利用率信号很可能准确地表示系统中各个任务的“大小”;
- 所有任务应该已经获得足够的CPU容量,而不管它们的优先级值如何;
- 由于存在剩余容量,所有任务必须定期阻塞/休眠,并且唤醒时的平衡就足够了。
一旦一个CPU超过80%的临界点,上述三个假设中至少有一个将变得不正确。在这种情况下,整个根域都会被标记为“过度利用”,EAS会被禁用,并且负载平衡器会被重新启用。通过这样做,调度程序会回退到基于负载的算法来进行唤醒和负载平衡,在CPU绑定条件下提供更好的任务优先级尊重。
由于过度利用的概念在很大程度上依赖于检测系统中是否有空闲时间,因此还需要考虑由较高(比CFS更高)调度类(以及IRQ)“窃取”的CPU容量。因此,过度利用的检测不仅考虑了由CFS任务使用的容量,还考虑了其他调度类和IRQ使用的容量。
6. EAS的依赖和要求
能量感知调度取决于系统的CPU具有特定的硬件属性,并且内核的其他特性已启用。本节列出了这些依赖关系,并提供了如何满足这些依赖关系的提示。
6.1 - 非对称CPU拓扑
如介绍中所述,目前EAS仅支持具有非对称CPU拓扑的平台。在构建调度域时,此要求会在运行时通过查找调度域构建时是否存在SD_ASYM_CPUCAPACITY_FULL标志来进行检查。
请注意,EAS与SMP并不根本上不兼容,但在SMP平台上尚未观察到显著的节省。如果有证据证明相反,这一限制可能会在未来得到修改。
6.2 - 能量模型存在
EAS使用平台的能量模型来估计调度决策对能量的影响。因此,您的平台必须向能量模型框架提供功耗成本表,以便启动EAS。要做到这一点,请参阅设备能量模型中独立EM框架的文档。
还请注意,必须在注册能量模型后重新构建调度域以启动EAS。
EAS使用能量模型来对能量使用进行预测决策,因此在检查任务放置的可能选项时更加关注差异。对于EAS来说,能量模型的功耗值是以毫瓦或“抽象刻度”表示都无关紧要。
6.3 - 能量模型复杂性
任务唤醒路径对延迟非常敏感。当平台的能量模型过于复杂(CPU太多、性能域太多、性能状态太多等)时,在唤醒路径中使用它的成本可能变得不可接受。能量感知唤醒算法的复杂性为:
C = Nd * (Nc + Ns)
其中:Nd为性能域的数量;Nc为CPU的数量;Ns为总的OPP数量(例如,对于具有每个4个OPP的两个性能域,Ns = 8)。
在构建调度域时,将在根域级别执行复杂性检查。如果在根域上的C高于完全任意的EM_MAX_COMPLEXITY阈值(在撰写本文时为2048),EAS将不会在该根域上启动。
如果您确实希望使用EAS,但您的平台的能量模型复杂性太高,无法在单个根域中使用,那么您只剩下两种可能的选择:
- 使用独占cpusets将系统分成单独的较小的根域,并在每个根域上本地启用EAS。这种选择的好处是可以立即使用,但缺点是会阻止根域之间的负载平衡,这可能导致整体系统不平衡;
- 提交补丁以减少EAS唤醒算法的复杂性,从而使其能够在合理的时间内处理更大的能量模型。
6.4 - Schedutil调频调度器
EAS试图预测CPU将在不久的将来运行的性能点,以估计它们的能量消耗。为了做到这一点,假设CPU的性能点遵循其利用率。
尽管很难在实践中提供关于这一假设准确性的硬性保证(因为硬件可能不会按照指示执行),但与其他CPUFreq调频调度器相比,schedutil至少“请求”使用利用率信号计算的频率。因此,与EAS一起使用的唯一合理调度器是schedutil,因为它是唯一提供频率请求和能量预测之间一定程度一致性的调度器。
不支持将EAS与schedutil以外的任何其他调度器一起使用。
6.5 - 尺度不变的利用率信号
为了在所有性能状态下跨CPU进行准确的预测,EAS需要频率不变和CPU不变的PELT信号。这些信号可以使用架构定义的arch_scale{cpu,freq}_capacity()回调函数获得。
不支持在不实现这两个回调的平台上使用EAS。
6.6 - 多线程(SMT)
目前的EAS形式不支持SMT,并且无法利用多线程硬件来节省能量。EAS将线程视为独立的CPU,这实际上可能对性能和能量都不利。
不支持在SMT上使用EAS。