← Documents Documentation/scheduler/sched-nice-design.rst GitHub 원문 ↗

Linux 6.18.37 · Scheduler

nice 값과 CFS weight 설계

nice level을 timeslice가 아니라 상대 weight로 해석하고 기하급수 비율을 사용하는 이유를 설명합니다.

Source pathDocumentation/scheduler/sched-nice-design.rst
Source versionLinux v6.18.37
TranslationDUJINLABS 전문 번역 + 해설

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

1. 요약·해설

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

선형 timeslice 방식의 문제

sched-nice-design.rst:1-63

과거 scheduler는 nice 값을 고정 timeslice에 직접 연결했습니다. 이 방식은 절대 timeslice와 HZ 설정에 의미가 묶이고, 낮은 우선순위 구간에서 작은 nice 변화가 실행 시간 비율을 지나치게 크게 바꾸는 문제가 있었습니다.

nice 변화는 일정한 상대 비율로 해석
nice 변화weight 관계CPU share 해석
0 → +1weight 약 1/1.25경쟁 task가 같다면 share 감소
0 → -1weight 약 1.25배경쟁 task가 같다면 share 증가
두 task가 같은 nice동일 weight각각 약 50%
한 task가 1단계 높음약 1.25 : 1약 55.6% : 44.4%

기하급수 weight 표를 사용하면 어느 nice 구간에서도 한 단계 변화가 거의 같은 상대 비율을 뜻합니다.

load weight와 독립적인 granularity

sched-nice-design.rst:64-113

CFS는 task의 실제 실행 시간을 weight로 정규화한 vruntime을 비교합니다. weight가 큰 task는 같은 실제 시간을 실행해도 vruntime이 천천히 증가하므로 더 많은 CPU share를 얻습니다. latency와 최소 실행 단위는 별도 tunable이 담당하여 nice 의미와 granularity를 분리합니다.

2. 영어 원문 전체

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

원문 전체 펼치기
1 =====================
2 Scheduler Nice Design
3 =====================
4
5 This document explains the thinking about the revamped and streamlined
6 nice-levels implementation in the new Linux scheduler.
7
8 Nice levels were always pretty weak under Linux and people continuously
9 pestered us to make nice +19 tasks use up much less CPU time.
10
11 Unfortunately that was not that easy to implement under the old
12 scheduler, (otherwise we'd have done it long ago) because nice level
13 support was historically coupled to timeslice length, and timeslice
14 units were driven by the HZ tick, so the smallest timeslice was 1/HZ.
15
16 In the O(1) scheduler (in 2003) we changed negative nice levels to be
17 much stronger than they were before in 2.4 (and people were happy about
18 that change), and we also intentionally calibrated the linear timeslice
19 rule so that nice +19 level would be _exactly_ 1 jiffy. To better
20 understand it, the timeslice graph went like this (cheesy ASCII art
21 alert!)::
22
23
24 A
25 \ | [timeslice length]
26 \ |
27 \ |
28 \ |
29 \ |
30 \|___100msecs
31 |^ . _
32 | ^ . _
33 | ^ . _
34 -*----------------------------------*-----> [nice level]
35 -20 | +19
36 |
37 |
38
39 So that if someone wanted to really renice tasks, +19 would give a much
40 bigger hit than the normal linear rule would do. (The solution of
41 changing the ABI to extend priorities was discarded early on.)
42
43 This approach worked to some degree for some time, but later on with
44 HZ=1000 it caused 1 jiffy to be 1 msec, which meant 0.1% CPU usage which
45 we felt to be a bit excessive. Excessive _not_ because it's too small of
46 a CPU utilization, but because it causes too frequent (once per
47 millisec) rescheduling. (and would thus trash the cache, etc. Remember,
48 this was long ago when hardware was weaker and caches were smaller, and
49 people were running number crunching apps at nice +19.)
50
51 So for HZ=1000 we changed nice +19 to 5msecs, because that felt like the
52 right minimal granularity - and this translates to 5% CPU utilization.
53 But the fundamental HZ-sensitive property for nice+19 still remained,
54 and we never got a single complaint about nice +19 being too _weak_ in
55 terms of CPU utilization, we only got complaints about it (still) being
56 too _strong_ :-)
57
58 To sum it up: we always wanted to make nice levels more consistent, but
59 within the constraints of HZ and jiffies and their nasty design level
60 coupling to timeslices and granularity it was not really viable.
61
62 The second (less frequent but still periodically occurring) complaint
63 about Linux's nice level support was its asymmetry around the origin
64 (which you can see demonstrated in the picture above), or more
65 accurately: the fact that nice level behavior depended on the _absolute_
66 nice level as well, while the nice API itself is fundamentally
67 "relative":
68
69 int nice(int inc);
70
71 asmlinkage long sys_nice(int increment)
72
73 (the first one is the glibc API, the second one is the syscall API.)
74 Note that the 'inc' is relative to the current nice level. Tools like
75 bash's "nice" command mirror this relative API.
76
77 With the old scheduler, if you for example started a niced task with +1
78 and another task with +2, the CPU split between the two tasks would
79 depend on the nice level of the parent shell - if it was at nice -10 the
80 CPU split was different than if it was at +5 or +10.
81
82 A third complaint against Linux's nice level support was that negative
83 nice levels were not 'punchy enough', so lots of people had to resort to
84 run audio (and other multimedia) apps under RT priorities such as
85 SCHED_FIFO. But this caused other problems: SCHED_FIFO is not starvation
86 proof, and a buggy SCHED_FIFO app can also lock up the system for good.
87
88 The new scheduler in v2.6.23 addresses all three types of complaints:
89
90 To address the first complaint (of nice levels being not "punchy"
91 enough), the scheduler was decoupled from 'time slice' and HZ concepts
92 (and granularity was made a separate concept from nice levels) and thus
93 it was possible to implement better and more consistent nice +19
94 support: with the new scheduler nice +19 tasks get a HZ-independent
95 1.5%, instead of the variable 3%-5%-9% range they got in the old
96 scheduler.
97
98 To address the second complaint (of nice levels not being consistent),
99 the new scheduler makes nice(1) have the same CPU utilization effect on
100 tasks, regardless of their absolute nice levels. So on the new
101 scheduler, running a nice +10 and a nice 11 task has the same CPU
102 utilization "split" between them as running a nice -5 and a nice -4
103 task. (one will get 55% of the CPU, the other 45%.) That is why nice
104 levels were changed to be "multiplicative" (or exponential) - that way
105 it does not matter which nice level you start out from, the 'relative
106 result' will always be the same.
107
108 The third complaint (of negative nice levels not being "punchy" enough
109 and forcing audio apps to run under the more dangerous SCHED_FIFO
110 scheduling policy) is addressed by the new scheduler almost
111 automatically: stronger negative nice levels are an automatic
112 side-effect of the recalibrated dynamic range of nice levels.
113

3. 한국어 전문 번역

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

Nice 설계를 다시 손본 이유

1-14

이 문서는 새 Linux scheduler에서 nice level 구현을 개편하고 단순화할 때 어떤 판단을 했는지 설명한다.

Linux의 nice level은 오래전부터 효과가 약하다는 평가를 받았다. 특히 사용자는 nice +19로 낮춘 task가 CPU 시간을 훨씬 적게 사용하기를 계속 요구했다.

그러나 옛 scheduler에서 이를 구현하기는 쉽지 않았다. Nice level 지원이 역사적으로 timeslice 길이에 결합되어 있었고, timeslice의 단위는 HZ tick으로 결정되었기 때문이다. 따라서 표현할 수 있는 가장 짧은 timeslice는 1/HZ였다.

O(1) scheduler의 선형 timeslice 규칙

16-41

2003년의 O(1) scheduler에서는 음수 nice level이 Linux 2.4 때보다 훨씬 강한 효과를 내도록 바꾸었고, 이 변경은 좋은 반응을 얻었다. 동시에 선형 timeslice 규칙을 조정해 nice +19의 timeslice가 정확히 1 jiffy가 되도록 했다.

원문의 그림은 가로축에 nice level -20부터 +19를, 세로축에 timeslice 길이를 놓는다. 음수 nice 구간에서는 nice 값이 증가할수록 timeslice가 거의 선형으로 감소한다. nice 0 부근에서는 약 100 ms에 도달하고, 양수 nice 구간에서는 +19의 1 jiffy 지점까지 더 가파르게 내려간다. 홈페이지의 구조화 도식은 이 두 구간과 +19의 끝점을 같은 관계로 다시 그린 것이다.

사용자가 task의 우선순위를 실제로 크게 낮추고 싶다면 +19가 보통의 선형 규칙보다 훨씬 큰 불이익을 주도록 만든 설계였다. Priority 범위를 넓히기 위해 ABI를 변경하는 방안은 초기 단계에서 폐기되었다.

HZ 의존성과 지나치게 짧은 실행 구간

43-60

이 방식은 한동안 어느 정도 동작했지만 HZ=1000 설정이 보편화되면서 문제가 드러났다. 이 설정에서는 1 jiffy가 1 ms이므로 nice +19 task가 얻는 CPU 비율은 약 0.1%가 된다.

문제는 CPU 사용률이 너무 낮다는 데 있지 않았다. 1 ms마다 재스케줄링이 일어나 cache를 계속 교란한다는 점이 더 큰 문제였다. 당시 하드웨어는 지금보다 느렸고 cache도 작았으며, nice +19로 수치 계산 프로그램을 실행하는 사용 사례도 있었다.

그래서 HZ=1000일 때 nice +19의 timeslice를 최소 실행 단위로 적절하다고 본 5 ms로 바꾸었다. 이는 약 5%의 CPU 사용률에 해당한다. 하지만 nice +19의 동작이 HZ에 따라 달라지는 근본 문제는 남았다. 사용자 불만도 +19가 너무 약하다는 쪽이 아니라 여전히 너무 많은 CPU를 얻는다는 쪽이었다.

결국 nice level을 더 일관되게 만들고 싶어도 HZ, jiffy, timeslice, 실행 granularity가 설계 단계에서 서로 얽혀 있는 한 제대로 해결하기 어려웠다.

상대 API와 절대 nice 값 사이의 불일치

62-80

두 번째로 반복해서 제기된 문제는 nice level의 효과가 원점을 기준으로 대칭적이지 않다는 점이었다. 더 정확히 말하면 nice API는 본질적으로 상대적인데 실제 CPU 배분은 task의 절대 nice 값에도 의존했다.

int nice(int inc);

asmlinkage long sys_nice(int increment)

첫 번째 선언은 glibc API이고 두 번째 선언은 system call API다. 두 API에서 inc 또는 increment는 현재 nice level에 더할 상대값이다. Bash의 nice 명령 같은 도구도 이 상대 API를 그대로 반영한다.

옛 scheduler에서는 예를 들어 nice +1인 task와 nice +2인 task를 실행하더라도 둘 사이의 CPU 배분이 parent shell의 nice 값에 따라 달라졌다. Parent가 nice -10일 때와 +5 또는 +10일 때 두 task가 얻는 비율이 같지 않았다.

음수 nice와 SCHED_FIFO의 위험

82-88

세 번째 문제는 음수 nice level의 효과가 충분히 강하지 않다는 것이었다. 그 결과 많은 사용자가 audio와 multimedia application을 SCHED_FIFO 같은 real-time priority로 실행해야 했다. 하지만 SCHED_FIFO는 starvation을 방지하지 않으므로 결함이 있는 application 하나가 다른 task의 실행을 막고 시스템 전체를 사실상 멈출 수 있다.

Linux v2.6.23의 새 scheduler는 앞의 세 가지 문제를 모두 다루었다.

v2.6.23 scheduler의 해결 방식

90-112

첫 번째 문제를 해결하기 위해 scheduler는 time slice와 HZ 개념으로부터 nice level을 분리했다. 실행 granularity도 nice와 별도의 개념으로 만들었다. 그 결과 nice +19 task는 옛 scheduler의 HZ 의존적인 3%, 5%, 9% 범위 대신 HZ와 무관하게 약 1.5%의 CPU를 얻도록 일관되게 구현할 수 있었다.

두 번째 문제를 해결하기 위해 nice 값을 한 단계 바꾸었을 때 CPU 사용률에 미치는 상대 효과가 절대 nice 값과 무관하도록 만들었다. 따라서 nice +10과 +11인 두 task의 CPU 배분은 nice -5와 -4인 두 task의 배분과 같다. 한쪽이 약 55%, 다른 쪽이 약 45%를 얻는다.

이를 위해 nice level의 weight를 선형이 아니라 곱셈형, 즉 지수형으로 바꾸었다. 시작 nice 값이 어디이든 한 단계 차이가 만드는 상대 비율이 같아진다.

세 번째 문제도 재조정된 nice dynamic range의 결과로 거의 자동으로 해결되었다. 음수 nice의 weight가 더 강해졌으므로, 높은 응답성이 필요한 application이 더 위험한 SCHED_FIFO 정책에 의존해야 할 필요가 줄었다.