← Documents Documentation/scheduler/schedutil.rst GitHub 원문 ↗

Linux 6.18.37 · Scheduler

Schedutil과 PELT 기반 DVFS

PELT, frequency invariance, UTIL_EST와 UCLAMP를 결합해 schedutil governor가 주파수를 선택하는 경로를 설명합니다.

Source pathDocumentation/scheduler/schedutil.rst
Source versionLinux v6.18.37
TranslationDUJINLABS 전문 번역 + 해설

요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.

1. 요약·해설

원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.

PELT 신호

schedutil.rst:1-44

PELT는 runnable, running과 load 상태를 지수 감쇠 누적으로 표현합니다. 오래된 실행 이력의 영향은 줄고 최근 활동이 더 크게 반영됩니다. task 신호를 cfs_rq와 CPU 단위로 합성할 수 있도록 같은 시간 기반과 scale을 사용합니다.

Frequency와 CPU invariance

schedutil.rst:45-92

같은 wall time을 실행해도 낮은 주파수 CPU가 처리한 실제 일은 적습니다. frequency invariance는 현재 주파수 대비 최대 성능으로 실행 기여도를 보정하고, CPU invariance는 서로 다른 micro-architecture의 최대 capacity 차이를 보정합니다.

UTIL_EST와 UCLAMP

schedutil.rst:93-119

UTIL_EST는 sleep 뒤 다시 깨어난 task의 utilization이 PELT로 천천히 올라가는 문제를 줄이기 위해 최근 실행 구간의 추정치를 제공합니다. 그 결과에 uclamp_min/max를 적용해 정책상 최소·최대 성능 범위를 반영합니다.

Scheduler event에서 DVFS 요청까지

schedutil.rst:120-173
schedutil frequency 결정 경로
PELT utilUTIL_ESTUCLAMPcapacity margintarget frequencycpufreq driver

scheduler가 갱신한 utilization을 governor가 capacity 대비 비율로 바꾸고 rate limit을 거쳐 cpufreq driver에 목표 주파수를 전달합니다.

schedutil은 enqueue, dequeue, tick과 migration 등 scheduler utilization이 바뀌는 지점에서 update hook을 받습니다. fast-switch를 지원하면 scheduler context에서 빠르게 주파수를 바꾸고, 그렇지 않으면 irq_work와 kthread 경로로 느린 driver 호출을 넘깁니다.

관찰할 때는 governor 계산값만 보지 말고 policy가 묶는 CPU 집합, transition latency, up/down rate limit과 thermal cooling 상태를 함께 확인해야 합니다. 목표값과 실제 clock 사이의 차이는 scheduler가 아니라 cpufreq driver 또는 firmware 제약에서 생길 수 있습니다.

2. 영어 원문 전체

번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.

원문 전체 펼치기
1 =========
2 Schedutil
3 =========
4
5 .. note::
6
7 All this assumes a linear relation between frequency and work capacity,
8 we know this is flawed, but it is the best workable approximation.
9
10
11 PELT (Per Entity Load Tracking)
12 ===============================
13
14 With PELT we track some metrics across the various scheduler entities, from
15 individual tasks to task-group slices to CPU runqueues. As the basis for this
16 we use an Exponentially Weighted Moving Average (EWMA), each period (1024us)
17 is decayed such that y^32 = 0.5. That is, the most recent 32ms contribute
18 half, while the rest of history contribute the other half.
19
20 Specifically:
21
22 ewma_sum(u) := u_0 + u_1*y + u_2*y^2 + ...
23
24 ewma(u) = ewma_sum(u) / ewma_sum(1)
25
26 Since this is essentially a progression of an infinite geometric series, the
27 results are composable, that is ewma(A) + ewma(B) = ewma(A+B). This property
28 is key, since it gives the ability to recompose the averages when tasks move
29 around.
30
31 Note that blocked tasks still contribute to the aggregates (task-group slices
32 and CPU runqueues), which reflects their expected contribution when they
33 resume running.
34
35 Using this we track 2 key metrics: 'running' and 'runnable'. 'Running'
36 reflects the time an entity spends on the CPU, while 'runnable' reflects the
37 time an entity spends on the runqueue. When there is only a single task these
38 two metrics are the same, but once there is contention for the CPU 'running'
39 will decrease to reflect the fraction of time each task spends on the CPU
40 while 'runnable' will increase to reflect the amount of contention.
41
42 For more detail see: kernel/sched/pelt.c
43
44
45 Frequency / CPU Invariance
46 ==========================
47
48 Because consuming the CPU for 50% at 1GHz is not the same as consuming the CPU
49 for 50% at 2GHz, nor is running 50% on a LITTLE CPU the same as running 50% on
50 a big CPU, we allow architectures to scale the time delta with two ratios, one
51 Dynamic Voltage and Frequency Scaling (DVFS) ratio and one microarch ratio.
52
53 For simple DVFS architectures (where software is in full control) we trivially
54 compute the ratio as::
55
56 f_cur
57 r_dvfs := -----
58 f_max
59
60 For more dynamic systems where the hardware is in control of DVFS we use
61 hardware counters (Intel APERF/MPERF, ARMv8.4-AMU) to provide us this ratio.
62 For Intel specifically, we use::
63
64 APERF
65 f_cur := ----- * P0
66 MPERF
67
68 4C-turbo; if available and turbo enabled
69 f_max := { 1C-turbo; if turbo enabled
70 P0; otherwise
71
72 f_cur
73 r_dvfs := min( 1, ----- )
74 f_max
75
76 We pick 4C turbo over 1C turbo to make it slightly more sustainable.
77
78 r_cpu is determined as the ratio of highest performance level of the current
79 CPU vs the highest performance level of any other CPU in the system.
80
81 r_tot = r_dvfs * r_cpu
82
83 The result is that the above 'running' and 'runnable' metrics become invariant
84 of DVFS and CPU type. IOW. we can transfer and compare them between CPUs.
85
86 For more detail see:
87
88 - kernel/sched/pelt.h:update_rq_clock_pelt()
89 - arch/x86/kernel/smpboot.c:"APERF/MPERF frequency ratio computation."
90 - Documentation/scheduler/sched-capacity.rst:"1. CPU Capacity + 2. Task utilization"
91
92
93 UTIL_EST
94 ========
95
96 Because periodic tasks have their averages decayed while they sleep, even
97 though when running their expected utilization will be the same, they suffer a
98 (DVFS) ramp-up after they are running again.
99
100 To alleviate this (a default enabled option) UTIL_EST drives an Infinite
101 Impulse Response (IIR) EWMA with the 'running' value on dequeue -- when it is
102 highest. UTIL_EST filters to instantly increase and only decay on decrease.
103
104 A further runqueue wide sum (of runnable tasks) is maintained of:
105
106 util_est := \Sum_t max( t_running, t_util_est_ewma )
107
108 For more detail see: kernel/sched/fair.c:util_est_dequeue()
109
110
111 UCLAMP
112 ======
113
114 It is possible to set effective u_min and u_max clamps on each CFS or RT task;
115 the runqueue keeps an max aggregate of these clamps for all running tasks.
116
117 For more detail see: include/uapi/linux/sched/types.h
118
119
120 Schedutil / DVFS
121 ================
122
123 Every time the scheduler load tracking is updated (task wakeup, task
124 migration, time progression) we call out to schedutil to update the hardware
125 DVFS state.
126
127 The basis is the CPU runqueue's 'running' metric, which per the above it is
128 the frequency invariant utilization estimate of the CPU. From this we compute
129 a desired frequency like::
130
131 max( running, util_est ); if UTIL_EST
132 u_cfs := { running; otherwise
133
134 clamp( u_cfs + u_rt , u_min, u_max ); if UCLAMP_TASK
135 u_clamp := { u_cfs + u_rt; otherwise
136
137 u := u_clamp + u_irq + u_dl; [approx. see source for more detail]
138
139 f_des := min( f_max, 1.25 u * f_max )
140
141 XXX IO-wait: when the update is due to a task wakeup from IO-completion we
142 boost 'u' above.
143
144 This frequency is then used to select a P-state/OPP or directly munged into a
145 CPPC style request to the hardware.
146
147 XXX: deadline tasks (Sporadic Task Model) allows us to calculate a hard f_min
148 required to satisfy the workload.
149
150 Because these callbacks are directly from the scheduler, the DVFS hardware
151 interaction should be 'fast' and non-blocking. Schedutil supports
152 rate-limiting DVFS requests for when hardware interaction is slow and
153 expensive, this reduces effectiveness.
154
155 For more information see: kernel/sched/cpufreq_schedutil.c
156
157
158 NOTES
159 =====
160
161 - On low-load scenarios, where DVFS is most relevant, the 'running' numbers
162 will closely reflect utilization.
163
164 - In saturated scenarios task movement will cause some transient dips,
165 suppose we have a CPU saturated with 4 tasks, then when we migrate a task
166 to an idle CPU, the old CPU will have a 'running' value of 0.75 while the
167 new CPU will gain 0.25. This is inevitable and time progression will
168 correct this. XXX do we still guarantee f_max due to no idle-time?
169
170 - Much of the above is about avoiding DVFS dips, and independent DVFS domains
171 having to re-learn / ramp-up when load shifts.
172
173

3. 한국어 전문 번역

영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.

PELT: Per Entity Load Tracking

1-43

이 문서의 설명은 frequency와 CPU가 처리할 수 있는 work capacity가 선형 관계라는 가정에 기반한다. 실제 hardware에서는 정확한 가정이 아니지만 현실적으로 사용할 수 있는 가장 나은 근사다.

PELT는 개별 task, task-group slice, CPU runqueue에 이르는 여러 scheduler entity의 metric을 추적한다. 기반 계산은 Exponentially Weighted Moving Average, 즉 EWMA다. 각 1024 us period마다 y^32=0.5가 되도록 과거 값을 감쇠한다. 따라서 가장 최근 32 ms가 전체 결과의 절반을, 그보다 오래된 이력이 나머지 절반을 차지한다.

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)다. task가 CPU나 group 사이를 이동할 때 평균값을 다시 조합할 수 있게 하는 핵심 성질이다.

blocked task도 task-group slice와 CPU runqueue aggregate에 계속 기여한다. 이는 그 task가 다시 실행 가능해졌을 때 예상되는 부하 기여도를 반영한다.

PELT로 추적하는 두 핵심 metric은 running과 runnable이다. running은 entity가 CPU에서 실제 실행한 시간을 나타내고, runnable은 runqueue에 실행 가능한 상태로 머문 시간을 나타낸다.

task가 하나뿐이면 두 metric은 같지만 CPU 경합이 생기면 달라진다. 각 task가 CPU를 실제로 차지하는 비율만큼 running은 낮아지고, 기다리는 task가 늘어나는 경합 정도를 반영하여 runnable은 높아진다.

Frequency와 CPU invariance

45-91

1 GHz CPU를 50% 사용한 것과 2 GHz CPU를 50% 사용한 것은 처리한 일의 양이 다르다. LITTLE CPU를 50% 사용한 것과 big CPU를 50% 사용한 것도 같지 않다. architecture는 시간 delta에 DVFS ratio와 microarchitecture ratio 두 값을 곱해 이런 차이를 보정할 수 있다.

software가 DVFS를 완전히 제어하는 단순한 architecture에서는 현재 frequency와 최대 frequency의 비율로 DVFS scaling ratio를 계산한다.

          f_cur
r_dvfs := -----
          f_max

hardware가 DVFS를 더 동적으로 제어하는 system에서는 Intel APERF/MPERF나 ARMv8.4 AMU 같은 hardware counter를 사용해 이 비율을 구한다.

Intel에서는 APERF와 MPERF를 이용해 현재 frequency를 다음처럼 계산한다.

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

        4C-turbo;  사용 가능하고 turbo가 켜진 경우
f_max = 1C-turbo;  turbo가 켜진 경우
        P0;        그 밖의 경우

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

1C turbo보다 4C turbo를 우선하는 이유는 조금 더 지속 가능한 최대 성능 기준을 사용하기 위해서다.

r_cpu는 현재 CPU의 최고 성능 수준과 system 내 다른 모든 CPU가 낼 수 있는 최고 성능 수준의 비율로 정한다.

r_tot = r_dvfs * r_cpu

최종적으로 앞에서 설명한 running과 runnable metric은 DVFS frequency와 CPU type에 독립적인 값이 된다. 따라서 서로 다른 CPU 사이에서 값을 옮기고 비교할 수 있다.

PELT 시간 보정 경로
scheduler clock deltar_dvfs = 현재 frequency / 기준 frequencyr_cpu = 현재 CPU capacity / system 최고 capacityr_tot = r_dvfs * r_cpufrequency·CPU invariant running/runnable

원시 실행 시간에 현재 frequency 비율과 CPU 최고 성능 비율을 함께 적용해 CPU 간 비교가 가능한 invariant signal을 만든다.

UTIL_EST와 UCLAMP

93-118

periodic task는 sleep하는 동안 PELT 평균값이 감쇠한다. 다시 실행되면 이전과 같은 utilization이 예상되더라도 signal이 낮아져 DVFS frequency가 다시 올라가는 ramp-up delay를 겪는다.

기본으로 활성화되는 UTIL_EST는 이 문제를 줄인다. task의 running 값이 가장 높은 dequeue 시점에 이 값을 이용해 Infinite Impulse Response, 즉 IIR EWMA를 구동한다. UTIL_EST filter는 추정치를 즉시 높일 수 있지만 낮아질 때만 점진적으로 감쇠한다.

runqueue에는 runnable task 전체에 대해 다음 합도 유지한다. 각 task의 현재 running 값과 util_est EWMA 중 큰 값을 선택해 더한다.

util_est := Sum_t max(t_running, t_util_est_ewma)

각 CFS 또는 RT task에는 effective u_min과 u_max clamp를 설정할 수 있다. runqueue는 현재 실행 가능한 모든 task의 clamp를 max aggregation으로 유지한다.

Schedutil과 DVFS

120-155

scheduler load tracking이 갱신될 때마다 schedutil callback을 호출해 hardware DVFS 상태를 갱신한다. task wakeup, task migration, 시간 경과가 대표적인 호출 계기다.

계산의 출발점은 CPU runqueue의 running metric이다. 앞에서 설명한 보정을 거친 값이므로 해당 CPU의 frequency-invariant utilization 추정치다. 원하는 frequency는 개념적으로 다음 순서로 계산한다.

          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;  // 근사식, 정확한 세부는 source 참조

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

UTIL_EST가 켜져 있으면 CFS 부하는 running과 util_est 중 큰 값이다. 여기에 RT utilization을 더하고 task uclamp 범위로 제한한다. 그 결과에 IRQ와 deadline utilization을 더해 전체 u를 만들고, 약 25% headroom을 둔 desired frequency를 계산한다.

I/O completion 때문에 task가 깨어나 발생한 update라면 위 식의 u에 I/O-wait boost가 적용된다.

계산한 frequency는 P-state나 OPP를 선택하는 데 사용하거나 CPPC 형식의 hardware request로 직접 변환한다.

Sporadic Task Model을 따르는 deadline task는 workload deadline을 만족하는 데 필요한 hard f_min을 계산할 수 있다.

callback은 scheduler에서 직접 호출되므로 DVFS hardware와의 상호작용은 빠르고 non-blocking이어야 한다. hardware 제어가 느리고 비싸면 schedutil이 DVFS request rate limiting을 지원하지만, 갱신을 제한하는 만큼 governor의 효과도 낮아진다.

schedutil desired frequency 계산
u_cfs = max(running, util_est)u_cfs + u_rtUCLAMP_MIN/MAX 적용u_irq와 u_dl 합산I/O wakeup이면 I/O-wait boostf_des = min(f_max, 1.25 * u * f_max)P-state / OPP / CPPC request

CFS 추정치부터 RT, uclamp, IRQ, deadline signal을 단계적으로 합친 뒤 headroom을 적용한다.

주의할 동작

158-172
  • DVFS가 가장 중요한 low-load 상황에서는 running 값이 실제 utilization을 상당히 가깝게 반영한다.
  • saturated 상황에서 task migration은 signal에 일시적인 dip을 만든다. task 네 개로 포화된 CPU에서 하나를 idle CPU로 옮기면 기존 CPU running은 0.75가 되고 새 CPU는 0.25를 얻는다. 피할 수 없는 과도 상태이며 시간이 지나 PELT가 이를 보정한다. 원문은 idle 시간이 전혀 없을 때도 f_max가 보장되는지 확인할 TODO를 남긴다.
  • 앞의 여러 장치는 DVFS frequency가 불필요하게 떨어지는 현상을 피하고, 서로 독립된 DVFS domain 사이로 load가 이동할 때 새 domain이 부하를 다시 학습하며 ramp-up해야 하는 비용을 줄이는 데 초점이 있다.
포화 CPU에서 task 하나를 이동한 직후
이동 전migration 직후시간 경과 후
01 CPU0: task 4개, running 1.0CPU0: task 3개, running 0.75CPU0 signal 재수렴
02 CPU1: idle, running 0CPU1: task 1개, running 0.25CPU1 signal 재수렴

총 부하는 보존되지만 PELT signal은 CPU 사이에 0.75와 0.25로 나뉘며, 이후 시간 경과에 따라 새 실행 상태로 수렴한다.