利用率夹紧(Utilization Clamping) 【ChatGPT】

发布时间 2023-12-11 21:41:38作者: 摩斯电码

利用率夹紧

1. 简介

利用率夹紧,也称为util clamp或uclamp,是一种调度器功能,允许用户空间帮助管理任务的性能需求。它是在v5.3版本中引入的。CGroup支持在v5.4中合并。

Uclamp是一种提示机制,允许调度器了解任务的性能要求和限制,因此它有助于调度器做出更好的决策。当使用schedutil cpufreq调度器时,util clamp也会影响CPU频率的选择。

由于调度器和schedutil都受PELT(util_avg)信号驱动,util clamp通过对该信号进行夹紧来实现其目标;因此得名。也就是说,通过夹紧利用率,我们使系统以一定的性能点运行。

正确理解util clamp的方式是将其视为对性能约束的请求或提示机制。它由两个可调参数组成:

  • UCLAMP_MIN,设置下限。
  • UCLAMP_MAX,设置上限。

这两个边界将确保任务在系统的性能范围内运行。UCLAMP_MIN意味着提升任务,而UCLAMP_MAX意味着限制任务。

用户可以告诉系统(调度器)一些任务需要在最低性能点上运行,以提供所需的用户体验。或者可以告诉系统,某些任务应受到限制,不得消耗过多资源,并且不得超过特定的性能点。将uclamp值视为性能点而不是利用率,从用户空间的角度来看是更好的抽象。

例如,游戏可以使用util clamp与其感知的每秒帧数(FPS)形成一个反馈循环。它可以动态增加其显示管线所需的最低性能点,以确保不会丢失任何帧。它还可以在知道接下来几百毫秒将发生计算密集型场景时,动态“预热”这些任务。

在移动硬件上,设备的能力差异很大,这种动态反馈循环为确保在任何系统的能力下提供最佳用户体验提供了极大的灵活性。

当然,静态配置也是可能的。确切的用法将取决于系统、应用程序和期望的结果。

另一个例子是在Android中,任务被分类为后台、前台、顶级应用程序等。利用率夹紧可以用来限制后台任务消耗的资源量,通过限制它们可以运行的性能点来保留资源以供重要任务使用,比如当前活动应用程序(顶级应用程序组)的任务。此外,这有助于限制它们消耗的功率。这在异构系统(例如Arm big.LITTLE)中更为明显;该约束将有助于使后台任务倾向于保持在小核心上运行,从而确保:

  • 大核心可以立即运行顶级应用程序任务。顶级应用程序任务是用户当前正在交互的任务,因此是系统中最重要的任务。
  • 它们不会在耗电量大的核心上运行并耗尽电池,即使它们是CPU密集型任务。

注意:
little cores:
CPUs with capacity < 1024
big cores:
CPUs with capacity = 1024

通过发出这些uclamp性能请求,或者更确切地说是提示,用户空间可以确保系统资源被最佳地使用,以提供最佳的用户体验。

另一个用例是帮助克服调度器利用率信号计算中固有的启动延迟。

另一方面,例如,一个需要以最大性能点运行的繁忙任务将遭受约200毫秒的延迟(PELT HALFIFE = 32毫秒),以便调度器意识到这一点。这已知会影响移动设备上的游戏等工作负载,因为由于选择所需的更高频率以使任务及时完成其工作而导致帧率下降。设置UCLAMP_MIN=1024将确保这样的任务在开始运行时始终看到最高的性能水平。

整体可见效果不仅仅是更好的用户体验/性能,而且还延伸到帮助实现更好的整体性能/瓦特,如果有效使用的话。

用户空间还可以与热管理子系统形成一个反馈循环,以确保设备不会过热到需要降频的程度。

SCHED_NORMAL/OTHER和SCHED_FIFO/RR都遵守uclamp请求/提示。

在SCHED_FIFO/RR情况下,uclamp提供了在任何性能点上运行RT任务的选项,而不是始终绑定到最大频率。这在运行在电池供电设备上的通用系统上可能很有用。

请注意,按设计RT任务没有每个任务的PELT信号,并且必须始终以恒定频率运行,以应对不确定的DVFS启动延迟。

请注意,使用schedutil总是意味着在RT任务唤醒时修改频率的单一延迟。使用uclamp不会改变这种成本。uclamp只有在选择要请求的频率时有所帮助,而schedutil总是为所有RT任务请求最大频率。

有关默认值,请参见第3.4节,有关如何更改RT任务默认值,请参见3.4.1节。

2. 设计

利用率夹紧是系统中每个任务的属性。它设置了其利用率信号的边界;作为一种影响调度器内部某些决策的偏置机制。

实际上,任务的利用率信号从未被夹紧。如果您在任何时间点检查PELT信号,您应该继续看到它们保持不变。夹紧只会在需要时发生,例如:当任务唤醒并且调度器需要选择一个适合它运行的CPU时。

由于util clamp的目标是允许请求任务运行的最低和最高性能点,它必须能够影响频率选择以及任务放置,才能发挥最大效果。这两者对CPU运行队列(rq简称)级别的利用率值都有影响,这带来了主要的设计挑战。

当任务在rq上唤醒时,rq的利用率信号将受到所有附加到其上的任务的uclamp设置的影响。例如,如果一个任务请求以UTIL_MIN = 512运行,那么rq的利用率信号需要尊重这个请求以及所有其他任务的所有其他请求。

为了能够聚合rq上所有任务的uclamp值,uclamp必须在每次入队/出队时进行一些维护,这是调度器的热路径。因此必须小心,因为任何减慢都会对许多用例产生重大影响,并可能在实践中阻碍其可用性。

处理这个问题的方式是将利用率范围划分为桶(struct uclamp_bucket),这使我们可以将搜索空间从rq上的每个任务减少到顶部桶上的一部分任务。

当任务入队时,匹配桶中的计数器会递增,而在出队时会递减。这使得跟踪rq的有效uclamp值变得更加容易。

当任务入队和出队时,我们会跟踪rq的当前有效uclamp值。有关此工作原理的详细信息,请参见第2.1节。

稍后,在任何想要确定rq的有效uclamp值的路径上,它只需要在需要做出决策的确切时刻读取rq的有效uclamp值。

对于任务放置的情况,目前只有能源感知和容量感知调度(EAS/CAS)才使用uclamp,这意味着它仅应用于异构系统。当任务唤醒时,调度器将查看每个rq的当前有效uclamp值,并将其与如果任务被入队到该rq时的潜在新值进行比较。优先选择将得到最节能组合的rq。

类似地,在schedutil中,当需要进行频率更新时,它将查看rq的当前有效uclamp值,该值受当前在那里入队的任务集的影响,并选择适当的频率以满足请求的约束。

其他路径,如设置过度利用状态(实际上禁用EAS),也使用uclamp。这些情况被认为是允许上述两个主要用例的必要维护,因此这里不会详细介绍,因为它们可能会随着实现细节的变化而改变。

2.1. 桶

                         [struct rq]

(bottom)                                                    (top)

  0                                                          1024
  |                                                           |
  +-----------+-----------+-----------+----   ----+-----------+
  |  Bucket 0 |  Bucket 1 |  Bucket 2 |    ...    |  Bucket N |
  +-----------+-----------+-----------+----   ----+-----------+
     :           :                                   :
     +- p0       +- p3                               +- p4
     :                                               :
     +- p1                                           +- p5
     :
     +- p2

备注
上面的图示是对内部数据结构的一种说明,而不是真实的内部数据结构描述。

为了在尝试决定任务入队/出队时的有效 uclamp 值时减少搜索空间,整个利用率范围被划分为 N 个桶,其中 N 通过设置 CONFIG_UCLAMP_BUCKETS_COUNT 在编译时配置,默认设置为 5。

rq 为每个 uclamp_id 可调参数设置了一个桶:[UCLAMP_MIN, UCLAMP_MAX]。

每个桶的范围为 1024/N。例如,默认值为 5 时,将有 5 个桶,每个桶将覆盖以下范围:

DELTA = round_closest(1024/5) = 204.8 = 205

桶 0: [0:204]
桶 1: [205:409]
桶 2: [410:614]
桶 3: [615:819]
桶 4: [820:1024]

当任务 p 具有以下可调参数时:

p->uclamp[UCLAMP_MIN] = 300
p->uclamp[UCLAMP_MAX] = 1024

被入队到 rq 时,将为 UCLAMP_MIN 增加桶 1,为 UCLAMP_MAX 增加桶 4,以反映 rq 中有一个在此范围内的任务。

然后,rq 会跟踪每个 uclamp_id 的当前有效 uclamp 值。

当任务 p 入队时,rq 的值变为:

// 更新桶逻辑在此处
rq->uclamp[UCLAMP_MIN] = max(rq->uclamp[UCLAMP_MIN], p->uclamp[UCLAMP_MIN])
// 对 UCLAMP_MAX 重复

类似地,当 p 出队时,rq 的值变为:

// 更新桶逻辑在此处
rq->uclamp[UCLAMP_MIN] = search_top_bucket_for_highest_value()
// 对 UCLAMP_MAX 重复

当所有桶都为空时,rq 的 uclamp 值将重置为系统默认值。有关默认值的详细信息,请参见第 3.4 节。

2.2. 最大聚合

Util clamp 被调整以满足需要最高性能点的任务的请求。

当多个任务附加到同一个 rq 时,util clamp 必须确保需要最高性能点的任务得到它,即使有另一个任务不需要或被禁止达到这一点。

例如,如果有多个任务附加到一个 rq,具有以下值:

p0->uclamp[UCLAMP_MIN] = 300
p0->uclamp[UCLAMP_MAX] = 900

p1->uclamp[UCLAMP_MIN] = 500
p1->uclamp[UCLAMP_MAX] = 500

假设 p0 和 p1 都入队到同一个 rq,则 UCLAMP_MIN 和 UCLAMP_MAX 都变为:

rq->uclamp[UCLAMP_MIN] = max(300, 500) = 500
rq->uclamp[UCLAMP_MAX] = max(900, 500) = 900

正如我们将在第 5.1 节中看到的,这种最大聚合是在使用 util clamp 时的一个限制之一,特别是对于用户空间希望节省电力的 UCLAMP_MAX 提示。

2.3. 分级聚合

如前所述,util clamp 是系统中每个任务的属性。但实际应用的(有效的)值可能受到不仅仅是任务或代表其的其他参与者(中间件库)所做请求的影响。

任何任务的有效 util clamp 值受到以下限制:

  • 由其附加到的 cgroup CPU 控制器定义的 uclamp 设置(如果有)。
  • (1)中的受限值然后受到系统范围的 uclamp 设置的进一步限制。

第 3 节将讨论接口,并将进一步扩展。

目前,可以简单地说,如果一个任务发出请求,其实际有效值将不得不遵守由 cgroup 和系统范围设置施加的一些限制。

即使在实际上超出约束时,系统仍将接受请求,但一旦任务移动到不同的 cgroup 或系统管理员修改系统设置,只有在新约束内才能满足请求。

换句话说,这种聚合不会在任务更改其 uclamp 值时导致错误,而是系统可能无法根据这些因素满足请求。

2.4. 范围

Uclamp 性能请求的范围为 0 到 1024(包括 1024)。

对于 cgroup 接口,使用百分比(即 0 到 100 包括在内)。与其他 cgroup 接口一样,您可以使用 'max' 代替 100。

3. 接口

3.1. 每个任务的接口

sched_setattr() 系统调用被扩展以接受两个新字段:

  • sched_util_min:请求系统在该任务运行时应该运行的最低性能点,或者说是性能下限。
  • sched_util_max:请求系统在该任务运行时应该运行的最高性能点,或者说是性能上限。

例如,以下情况有 40% 到 80% 的利用率约束:

attr->sched_util_min = 40% * 1024;
attr->sched_util_max = 80% * 1024;

当任务 @p 正在运行时,调度器应尽力确保它以 40% 的性能水平启动。如果任务运行的时间足够长,以至于其实际利用率超过 80%,则利用率或性能水平将被限制。

特殊值 -1 用于将 uclamp 设置重置为系统默认值。

请注意,使用 -1 将 uclamp 值重置为系统默认值与手动将 uclamp 值设置为系统默认值不同。这种区别很重要,因为如我们将在系统接口中看到的,RT 的默认值可能会发生变化。SCHED_NORMAL/OTHER 也可能在将来获得类似的旋钮。

3.2. cgroup 接口

CPU cgroup 控制器中有两个与 uclamp 相关的值:

  • cpu.uclamp.min
  • cpu.uclamp.max

当一个任务附加到 CPU 控制器时,其 uclamp 值将受到以下影响:

  • cpu.uclamp.min 是 cgroup v2 文档第 3-3 节中描述的一种保护机制。
  • 如果任务的 uclamp_min 值低于 cpu.uclamp.min,则任务将继承 cgroup cpu.uclamp.min 的值。
  • 在 cgroup 层次结构中,有效的 cpu.uclamp.min 是 (child, parent) 的最大值。
  • cpu.uclamp.max 是 cgroup v2 文档第 3-2 节中描述的一种限制机制。
  • 如果任务的 uclamp_max 值高于 cpu.uclamp.max,则任务将继承 cgroup cpu.uclamp.max 的值。
  • 在 cgroup 层次结构中,有效的 cpu.uclamp.max 是 (child, parent) 的最小值。

例如,给定以下参数:

p0->uclamp[UCLAMP_MIN] = // 系统默认值;
p0->uclamp[UCLAMP_MAX] = // 系统默认值;

p1->uclamp[UCLAMP_MIN] = 40% * 1024;
p1->uclamp[UCLAMP_MAX] = 50% * 1024;

cgroup0->cpu.uclamp.min = 20% * 1024;
cgroup0->cpu.uclamp.max = 60% * 1024;

cgroup1->cpu.uclamp.min = 60% * 1024;
cgroup1->cpu.uclamp.max = 100% * 1024;

当 p0 和 p1 附加到 cgroup0 时,值变为:

p0->uclamp[UCLAMP_MIN] = cgroup0->cpu.uclamp.min = 20% * 1024;
p0->uclamp[UCLAMP_MAX] = cgroup0->cpu.uclamp.max = 60% * 1024;

p1->uclamp[UCLAMP_MIN] = 40% * 1024; // 不变
p1->uclamp[UCLAMP_MAX] = 50% * 1024; // 不变

当 p0 和 p1 附加到 cgroup1 时,值变为:

p0->uclamp[UCLAMP_MIN] = cgroup1->cpu.uclamp.min = 60% * 1024;
p0->uclamp[UCLAMP_MAX] = cgroup1->cpu.uclamp.max = 100% * 1024;

p1->uclamp[UCLAMP_MIN] = cgroup1->cpu.uclamp.min = 60% * 1024;
p1->uclamp[UCLAMP_MAX] = 50% * 1024; // 不变

请注意,cgroup 接口允许 cpu.uclamp.max 的值低于 cpu.uclamp.min。其他接口不允许这样做。

3.3. 系统接口

3.3.1 sched_util_clamp_min

允许的 UCLAMP_MIN 范围的系统范围限制。默认情况下,它设置为 1024,这意味着任务的允许的有效 UCLAMP_MIN 范围为 [0:1024]。通过将其更改为例如 512,范围将减少为 [0:512]。这对于限制任务被允许获取多少提升是有用的。

任务的请求超过此旋钮值将仍然成功,但直到它超过 p->uclamp[UCLAMP_MIN] 时才会被满足。

该值必须小于或等于 sched_util_clamp_max。

3.3.2 sched_util_clamp_max

允许的 UCLAMP_MAX 范围的系统范围限制。默认情况下,它设置为 1024,这意味着任务的允许的有效 UCLAMP_MAX 范围为 [0:1024]。

通过将其更改为例如 512,有效的允许范围将减少为 [0:512]。这意味着没有任务可以运行超过 512,这意味着所有 rqs 也受到限制。换句话说,整个系统的性能容量被限制为其一半。

这对于限制系统的整体最大性能点是有用的。例如,在电量不足时限制性能,或者当系统处于空闲状态或屏幕关闭时限制对更耗能的性能级别的访问。

任务的请求超过此旋钮值将仍然成功,但直到它超过 p->uclamp[UCLAMP_MAX] 时才会被满足。

该值必须大于或等于 sched_util_clamp_min。

3.4. 默认值

默认情况下,所有 SCHED_NORMAL/SCHED_OTHER 任务被初始化为:

p_fair->uclamp[UCLAMP_MIN] = 0
p_fair->uclamp[UCLAMP_MAX] = 1024

也就是说,默认情况下它们被提升到以最大性能点运行,如果在启动或运行时更改,还没有提出为什么我们应该提供这个,但可以在将来添加。

对于 SCHED_FIFO/SCHED_RR 任务:

p_rt->uclamp[UCLAMP_MIN] = 1024
p_rt->uclamp[UCLAMP_MAX] = 1024

也就是说,默认情况下它们被提升到以系统的最大性能点运行,保留了 RT 任务的历史行为。

RT 任务的默认 uclamp_min 值可以通过 sysctl 在启动或运行时进行修改。请参见下面的部分。

3.4.1 sched_util_clamp_min_rt_default

在电池供电设备上以最大性能点运行 RT 任务是昂贵的,也是不必要的。为了让系统开发人员能够为这些任务提供良好的性能保证,而不必一直以最大性能点运行,这个 sysctl 旋钮允许调整最佳提升值以满足系统要求,而不必一直以最大性能点运行。

鼓励应用程序开发人员使用每个任务的 util clamp 接口来确保它们具有性能和功耗意识。理想情况下,这个旋钮应该由系统设计者设置为 0,并将管理性能要求的任务留给应用程序。

4. 如何使用 util clamp

Util clamp 提倡用户空间辅助的功耗和性能管理概念。在调度器级别,不需要任何信息来做出最佳决策。然而,通过 util clamp,用户空间可以提示调度器做出更好的任务放置和频率选择决策。

最好的结果是通过不对应用程序运行的系统做任何假设,并与动态监视和调整的反馈循环结合使用。最终,这将允许更好的用户体验和更好的性能/功耗比。

对于某些系统和用例,静态设置将有助于实现良好的结果。在这种情况下,可移植性将是一个问题。在每个系统上,以 100、200 或 1024 进行多少工作是不同的。除非有一个特定的目标系统,否则应避免静态设置。

有足够的可能性创建一个基于 util clamp 的整个框架或直接利用它的自包含应用程序。

4.1. 提升重要且对 DVFS 延迟敏感的任务

GUI 任务可能不忙于在其唤醒时驱动频率高。然而,它需要在特定时间窗口内完成其工作,以提供所需的用户体验。它在唤醒时需要的正确频率将取决于系统。在一些性能不足的系统上,它将很高,在其他性能过剩的系统上,它将很低或为 0。

这个任务可以每次错过截止时间时增加其 UCLAMP_MIN 值,以确保在下次唤醒时以更高的性能点运行。它应该尝试接近允许在任何特定系统上满足其截止时间的最低 UCLAMP_MIN 值,以实现该系统的最佳性能/功耗比。

在异构系统上,这个任务可能重要的是在更快的 CPU 上运行。

一般建议将输入视为性能级别或点,这将意味着任务放置和频率选择。

4.2. 限制后台任务

就像在介绍中为 Android 情况所解释的那样。任何应用程序都可以降低一些后台任务的 UCLAMP_MAX,这些任务不关心性能,但可能会变得繁忙并消耗系统资源。

4.3. 节能模式

sched_util_clamp_max 系统范围接口可以用于限制所有任务不以更高性能点运行,这通常是能源效率低下的。

这与通过降低 cpufreq 调节器的最大频率来实现相同,并且可以被认为是一个更方便的替代接口。

4.4. 每个应用程序的性能限制

中间件/实用程序可以为用户提供一个选项,在每次执行应用程序时设置 UCLAMP_MIN/MAX,以保证最低性能点和/或限制它从消耗系统功率而牺牲性能的这些应用程序。

如果你想要防止笔记本电脑在外出时因为编译内核而发热,并愿意牺牲性能以节省电力,但仍然希望保持浏览器的性能完好,uclamp 可以实现这一点。

5. 限制

5.1. 在某些情况下,使用 uclamp_max 限制频率会失败

假设任务 p0 被限制在 512 的性能点上:

p0->uclamp[UCLAMP_MAX] = 512

并且它与可以在任何性能点上自由运行的任务 p1 共享 rq:

p1->uclamp[UCLAMP_MAX] = 1024

由于最大聚合,rq 将被允许达到最大性能点:

rq->uclamp[UCLAMP_MAX] = max(512, 1024) = 1024

假设 p0 和 p1 的 UCLAMP_MIN 均为 0,则 rq 的频率选择将取决于任务的实际利用率值。

如果 p1 是一个小任务,但 p0 是一个 CPU 密集型任务,那么由于它们都在同一个 rq 上运行,p1 将导致频率限制被留在 rq 上,尽管 p1 可以在任何性能点上运行,实际上并不需要以那个频率运行。

5.2. UCLAMP_MAX 可能会破坏 PELT(util_avg)信号

PELT 假设随着信号增长,频率将始终增加,以确保 CPU 上始终有一些空闲时间。但是使用 UCLAMP_MAX,这种频率增加将被阻止,这可能会导致在某些情况下没有空闲时间。当没有空闲时间时,任务将陷入繁忙循环中,这将导致 util_avg 为 1024。

结合下面描述的问题,这可能会导致严重受限的任务与一个小的非受限任务共享 rq 时出现不希望的频率突增。

举例来说,如果任务 p 具有以下属性:

p0->util_avg = 300
p0->uclamp[UCLAMP_MAX] = 0

当其在空闲 CPU 上唤醒时,它将以该 CPU 能够运行的最低频率(Fmin)运行。最大 CPU 频率(Fmax)在这里也很重要,因为它指定了在该 CPU 上完成任务工作的最短计算时间。

rq->uclamp[UCLAMP_MAX] = 0

如果 Fmax/Fmin 的比率为 3,则最大值将是:

300 * (Fmax/Fmin) = 900

这表明 CPU 仍然会有空闲时间,因为 900 < 1024。然而,实际的 util_avg 不会是 900,而是介于 300 和 900 之间。只要有空闲时间,p->util_avg 的更新将会有一些误差,但不会与 Fmax/Fmin 成比例。

p0->util_avg = 300 + small_error

现在,如果 Fmax/Fmin 的比率为 4,则最大值将是:

300 * (Fmax/Fmin) = 1200

这将高于 1024,表明 CPU 没有空闲时间。当发生这种情况时,实际的 util_avg 将变为:

p0->util_avg = 1024

如果任务 p1 在此 CPU 上唤醒,具有以下属性:

p1->util_avg = 200
p1->uclamp[UCLAMP_MAX] = 1024

则根据最大聚合规则,CPU 的有效 UCLAMP_MAX 将为 1024。但由于受限的 p0 任务正在运行并受到严重限制,因此 rq->util_avg 将为:

p0->util_avg = 1024
p1->util_avg = 200
rq->util_avg = 1024
rq->uclamp[UCLAMP_MAX] = 1024

因此会导致频率突增,因为如果 p0 没有受到限制,我们应该得到:

p0->util_avg = 300
p1->util_avg = 200
rq->util_avg = 500

并在该 CPU 的中间性能点附近运行,而不是我们得到的 Fmax。

5.3. Schedutil 响应时间问题

schedutil 存在三个限制:

  • 硬件对于任何频率更改请求的响应需要一定的时间。在某些平台上可能需要几毫秒。
  • 非快速切换系统需要一个工作线程截止时间来唤醒并执行频率更改,这会增加可测量的开销。
  • schedutil rate_limit_us 在 rate_limit_us 窗口期间会丢弃任何请求。

如果一个相对较小的任务正在执行关键工作,并且在唤醒并开始运行时需要特定的性能点,那么所有这些限制将阻止它在预期的时间尺度内获得所需的性能。

这种限制不仅在使用 uclamp 时具有影响,而且在不再逐渐增加或减少时将更为普遍。我们可能会根据任务唤醒的顺序以及它们各自的 uclamp 值在不同频率之间跳跃。

我们认为这是底层系统能力的限制。

schedutil rate_limit_us 的行为有改进的空间,但对于 1 或 2 来说,可以做的不多。它们被认为是系统的硬限制。