summaryrefslogtreecommitdiff
path: root/Documentation/translations/zh_CN/scheduler/schedutil.rst
blob: 7c8d87f21c4221a528ca5a3e06526c17b66a8718 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/scheduler/schedutil.rst

:翻译:

  唐艺舟 Tang Yizhou <tangyeechou@gmail.com>

=========
Schedutil
=========

.. note::

   本文所有内容都假设频率和工作算力之间存在线性关系。我们知道这是有瑕疵的,
   但这是最可行的近似处理。

PELT(实体负载跟踪,Per Entity Load Tracking)
==============================================

通过PELT,我们跟踪了各种调度器实体的一些指标,从单个任务到任务组分片到CPU
运行队列。我们使用指数加权移动平均数(Exponentially Weighted Moving Average,
EWMA)作为其基础,每个周期(1024us)都会衰减,衰减速率满足y^32 = 0.5。
也就是说,最近的32ms贡献负载的一半,而历史上的其它时间则贡献另一半。

具体而言:

  ewma_sum(u) := u_0 + u_1*y + u_2*y^2 + ...

  ewma(u) = ewma_sum(u) / ewma_sum(1)

由于这本质上是一个无限几何级数的累加,结果是可组合的,即ewma(A) + ewma(B) = ewma(A+B)。
这个属性是关键,因为它提供了在任务迁移时重新组合平均数的能力。

请注意,阻塞态的任务仍然对累加值(任务组分片和CPU运行队列)有贡献,这反映了
它们在恢复运行后的预期贡献。

利用这一点,我们跟踪2个关键指标:“运行”和“可运行”。“运行”反映了一个调度实体
在CPU上花费的时间,而“可运行”反映了一个调度实体在运行队列中花费的时间。当只有
一个任务时,这两个指标是相同的,但一旦出现对CPU的争用,“运行”将减少以反映每个
任务在CPU上花费的时间,而“可运行”将增加以反映争用的激烈程度。

更多细节见:kernel/sched/pelt.c


频率 / CPU不变性
================

因为CPU频率在1GHz时利用率为50%和CPU频率在2GHz时利用率为50%是不一样的,同样
在小核上运行时利用率为50%和在大核上运行时利用率为50%是不一样的,我们允许架构
以两个比率来伸缩时间差,其中一个是动态电压频率升降(Dynamic Voltage and
Frequency Scaling,DVFS)比率,另一个是微架构比率。

对于简单的DVFS架构(软件有完全控制能力),我们可以很容易地计算该比率为::

            f_cur
  r_dvfs := -----
            f_max

对于由硬件控制DVFS的更多动态系统,我们使用硬件计数器(Intel APERF/MPERF,
ARMv8.4-AMU)来计算这一比率。具体到Intel,我们使用::

           APERF
  f_cur := ----- * P0
           MPERF

             4C-turbo;  如果可用并且使能了turbo
  f_max := { 1C-turbo;  如果使能了turbo
             P0;        其它情况

                    f_cur
  r_dvfs := min( 1, ----- )
                    f_max

我们选择4C turbo而不是1C turbo,以使其更持久性略微更强。

r_cpu被定义为当前CPU的最高性能水平与系统中任何其它CPU的最高性能水平的比率。

  r_tot = r_dvfs * r_cpu

其结果是,上述“运行”和“可运行”的指标变成DVFS无关和CPU型号无关了。也就是说,
我们可以在CPU之间转移和比较它们。

更多细节见:

 - kernel/sched/pelt.h:update_rq_clock_pelt()
 - arch/x86/kernel/smpboot.c:"APERF/MPERF frequency ratio computation."
 - Documentation/translations/zh_CN/scheduler/sched-capacity.rst:"1. CPU Capacity + 2. Task utilization"


UTIL_EST
========

由于周期性任务的平均数在睡眠时会衰减,而在运行时其预期利用率会和睡眠前相同,
因此它们在再次运行后会面临(DVFS)的上涨。

为了缓解这个问题,(一个默认使能的编译选项)UTIL_EST驱动一个无限脉冲响应
(Infinite Impulse Response,IIR)的EWMA,“运行”值在出队时是最高的。
UTIL_EST滤波使其在遇到更高值时立刻增加,而遇到低值时会缓慢衰减。

进一步,运行队列的(可运行任务的)利用率之和由下式计算:

  util_est := \Sum_t max( t_running, t_util_est_ewma )

更多细节见: kernel/sched/fair.c:util_est_dequeue()


UCLAMP
======

可以在每个CFS或RT任务上设置有效的u_min和u_max clamp值(译注:clamp可以理解
为类似滤波器的能力,它定义了有效取值范围的最大值和最小值);运行队列为所有正在
运行的任务保持这些clamp的最大聚合值。

更多细节见: include/uapi/linux/sched/types.h


Schedutil / DVFS
================

每当调度器的负载跟踪被更新时(任务唤醒、任务迁移、时间流逝),我们都会调用
schedutil来更新硬件DVFS状态。

其基础是CPU运行队列的“运行”指标,根据上面的内容,它是CPU的频率不变的利用率
估计值。由此我们计算出一个期望的频率,如下::

             max( running, util_est );  如果使能UTIL_EST
  u_cfs := { running;                   其它情况

               clamp( u_cfs + u_rt, u_min, u_max );  如果使能UCLAMP_TASK
  u_clamp := { u_cfs + u_rt;                         其它情况

  u := u_clamp + u_irq + u_dl;		[估计值。更多细节见源代码]

  f_des := min( f_max, 1.25 u * f_max )

关于IO-wait的说明:当发生更新是因为任务从IO完成中唤醒时,我们提升上面的“u”。

然后,这个频率被用来选择一个P-state或OPP,或者直接混入一个发给硬件的CPPC式
请求。

关于截止期限调度器的说明: 截止期限任务(偶发任务模型)使我们能够计算出满足
工作负荷所需的硬f_min值。

因为这些回调函数是直接来自调度器的,所以DVFS的硬件交互应该是“快速”和非阻塞的。
在硬件交互缓慢和昂贵的时候,schedutil支持DVFS请求限速,不过会降低效率。

更多信息见: kernel/sched/cpufreq_schedutil.c


注意
====

 - 在低负载场景下,DVFS是最相关的,“运行”的值将密切反映利用率。

 - 在负载饱和的场景下,任务迁移会导致一些瞬时性的使用率下降。假设我们有一个
   CPU,有4个任务占用导致其饱和,接下来我们将一个任务迁移到另一个空闲CPU上,
   旧的CPU的“运行”值将为0.75,而新的CPU将获得0.25。这是不可避免的,而且随着
   时间流逝将自动修正。另注,由于没有空闲时间,我们还能保证f_max值吗?

 - 上述大部分内容是关于避免DVFS下滑,以及独立的DVFS域发生负载迁移时不得不
   重新学习/提升频率。