]> git.ipfire.org Git - thirdparty/kernel/stable-queue.git/blob
430ebfa2179ea69729ca49d6a00628edfb1ee79d
[thirdparty/kernel/stable-queue.git] /
1 From de53fd7aedb100f03e5d2231cfce0e4993282425 Mon Sep 17 00:00:00 2001
2 From: Dave Chiluk <chiluk+linux@indeed.com>
3 Date: Tue, 23 Jul 2019 11:44:26 -0500
4 Subject: sched/fair: Fix low cpu usage with high throttling by removing expiration of cpu-local slices
5
6 From: Dave Chiluk <chiluk+linux@indeed.com>
7
8 commit de53fd7aedb100f03e5d2231cfce0e4993282425 upstream.
9
10 It has been observed, that highly-threaded, non-cpu-bound applications
11 running under cpu.cfs_quota_us constraints can hit a high percentage of
12 periods throttled while simultaneously not consuming the allocated
13 amount of quota. This use case is typical of user-interactive non-cpu
14 bound applications, such as those running in kubernetes or mesos when
15 run on multiple cpu cores.
16
17 This has been root caused to cpu-local run queue being allocated per cpu
18 bandwidth slices, and then not fully using that slice within the period.
19 At which point the slice and quota expires. This expiration of unused
20 slice results in applications not being able to utilize the quota for
21 which they are allocated.
22
23 The non-expiration of per-cpu slices was recently fixed by
24 'commit 512ac999d275 ("sched/fair: Fix bandwidth timer clock drift
25 condition")'. Prior to that it appears that this had been broken since
26 at least 'commit 51f2176d74ac ("sched/fair: Fix unlocked reads of some
27 cfs_b->quota/period")' which was introduced in v3.16-rc1 in 2014. That
28 added the following conditional which resulted in slices never being
29 expired.
30
31 if (cfs_rq->runtime_expires != cfs_b->runtime_expires) {
32 /* extend local deadline, drift is bounded above by 2 ticks */
33 cfs_rq->runtime_expires += TICK_NSEC;
34
35 Because this was broken for nearly 5 years, and has recently been fixed
36 and is now being noticed by many users running kubernetes
37 (https://github.com/kubernetes/kubernetes/issues/67577) it is my opinion
38 that the mechanisms around expiring runtime should be removed
39 altogether.
40
41 This allows quota already allocated to per-cpu run-queues to live longer
42 than the period boundary. This allows threads on runqueues that do not
43 use much CPU to continue to use their remaining slice over a longer
44 period of time than cpu.cfs_period_us. However, this helps prevent the
45 above condition of hitting throttling while also not fully utilizing
46 your cpu quota.
47
48 This theoretically allows a machine to use slightly more than its
49 allotted quota in some periods. This overflow would be bounded by the
50 remaining quota left on each per-cpu runqueueu. This is typically no
51 more than min_cfs_rq_runtime=1ms per cpu. For CPU bound tasks this will
52 change nothing, as they should theoretically fully utilize all of their
53 quota in each period. For user-interactive tasks as described above this
54 provides a much better user/application experience as their cpu
55 utilization will more closely match the amount they requested when they
56 hit throttling. This means that cpu limits no longer strictly apply per
57 period for non-cpu bound applications, but that they are still accurate
58 over longer timeframes.
59
60 This greatly improves performance of high-thread-count, non-cpu bound
61 applications with low cfs_quota_us allocation on high-core-count
62 machines. In the case of an artificial testcase (10ms/100ms of quota on
63 80 CPU machine), this commit resulted in almost 30x performance
64 improvement, while still maintaining correct cpu quota restrictions.
65 That testcase is available at https://github.com/indeedeng/fibtest.
66
67 Fixes: 512ac999d275 ("sched/fair: Fix bandwidth timer clock drift condition")
68 Signed-off-by: Dave Chiluk <chiluk+linux@indeed.com>
69 Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
70 Reviewed-by: Phil Auld <pauld@redhat.com>
71 Reviewed-by: Ben Segall <bsegall@google.com>
72 Cc: Ingo Molnar <mingo@redhat.com>
73 Cc: John Hammond <jhammond@indeed.com>
74 Cc: Jonathan Corbet <corbet@lwn.net>
75 Cc: Kyle Anderson <kwa@yelp.com>
76 Cc: Gabriel Munos <gmunoz@netflix.com>
77 Cc: Peter Oskolkov <posk@posk.io>
78 Cc: Cong Wang <xiyou.wangcong@gmail.com>
79 Cc: Brendan Gregg <bgregg@netflix.com>
80 Link: https://lkml.kernel.org/r/1563900266-19734-2-git-send-email-chiluk+linux@indeed.com
81 Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
82
83 ---
84 Documentation/scheduler/sched-bwc.txt | 45 +++++++++++++++++++++
85 kernel/sched/fair.c | 70 +++-------------------------------
86 kernel/sched/sched.h | 4 -
87 3 files changed, 52 insertions(+), 67 deletions(-)
88
89 --- a/Documentation/scheduler/sched-bwc.txt
90 +++ b/Documentation/scheduler/sched-bwc.txt
91 @@ -90,6 +90,51 @@ There are two ways in which a group may
92 In case b) above, even though the child may have runtime remaining it will not
93 be allowed to until the parent's runtime is refreshed.
94
95 +CFS Bandwidth Quota Caveats
96 +---------------------------
97 +Once a slice is assigned to a cpu it does not expire. However all but 1ms of
98 +the slice may be returned to the global pool if all threads on that cpu become
99 +unrunnable. This is configured at compile time by the min_cfs_rq_runtime
100 +variable. This is a performance tweak that helps prevent added contention on
101 +the global lock.
102 +
103 +The fact that cpu-local slices do not expire results in some interesting corner
104 +cases that should be understood.
105 +
106 +For cgroup cpu constrained applications that are cpu limited this is a
107 +relatively moot point because they will naturally consume the entirety of their
108 +quota as well as the entirety of each cpu-local slice in each period. As a
109 +result it is expected that nr_periods roughly equal nr_throttled, and that
110 +cpuacct.usage will increase roughly equal to cfs_quota_us in each period.
111 +
112 +For highly-threaded, non-cpu bound applications this non-expiration nuance
113 +allows applications to briefly burst past their quota limits by the amount of
114 +unused slice on each cpu that the task group is running on (typically at most
115 +1ms per cpu or as defined by min_cfs_rq_runtime). This slight burst only
116 +applies if quota had been assigned to a cpu and then not fully used or returned
117 +in previous periods. This burst amount will not be transferred between cores.
118 +As a result, this mechanism still strictly limits the task group to quota
119 +average usage, albeit over a longer time window than a single period. This
120 +also limits the burst ability to no more than 1ms per cpu. This provides
121 +better more predictable user experience for highly threaded applications with
122 +small quota limits on high core count machines. It also eliminates the
123 +propensity to throttle these applications while simultanously using less than
124 +quota amounts of cpu. Another way to say this, is that by allowing the unused
125 +portion of a slice to remain valid across periods we have decreased the
126 +possibility of wastefully expiring quota on cpu-local silos that don't need a
127 +full slice's amount of cpu time.
128 +
129 +The interaction between cpu-bound and non-cpu-bound-interactive applications
130 +should also be considered, especially when single core usage hits 100%. If you
131 +gave each of these applications half of a cpu-core and they both got scheduled
132 +on the same CPU it is theoretically possible that the non-cpu bound application
133 +will use up to 1ms additional quota in some periods, thereby preventing the
134 +cpu-bound application from fully using its quota by that same amount. In these
135 +instances it will be up to the CFS algorithm (see sched-design-CFS.rst) to
136 +decide which application is chosen to run, as they will both be runnable and
137 +have remaining quota. This runtime discrepancy will be made up in the following
138 +periods when the interactive application idles.
139 +
140 Examples
141 --------
142 1. Limit a group to 1 CPU worth of runtime.
143 --- a/kernel/sched/fair.c
144 +++ b/kernel/sched/fair.c
145 @@ -4106,8 +4106,6 @@ void __refill_cfs_bandwidth_runtime(stru
146
147 now = sched_clock_cpu(smp_processor_id());
148 cfs_b->runtime = cfs_b->quota;
149 - cfs_b->runtime_expires = now + ktime_to_ns(cfs_b->period);
150 - cfs_b->expires_seq++;
151 }
152
153 static inline struct cfs_bandwidth *tg_cfs_bandwidth(struct task_group *tg)
154 @@ -4129,8 +4127,7 @@ static int assign_cfs_rq_runtime(struct
155 {
156 struct task_group *tg = cfs_rq->tg;
157 struct cfs_bandwidth *cfs_b = tg_cfs_bandwidth(tg);
158 - u64 amount = 0, min_amount, expires;
159 - int expires_seq;
160 + u64 amount = 0, min_amount;
161
162 /* note: this is a positive sum as runtime_remaining <= 0 */
163 min_amount = sched_cfs_bandwidth_slice() - cfs_rq->runtime_remaining;
164 @@ -4147,61 +4144,17 @@ static int assign_cfs_rq_runtime(struct
165 cfs_b->idle = 0;
166 }
167 }
168 - expires_seq = cfs_b->expires_seq;
169 - expires = cfs_b->runtime_expires;
170 raw_spin_unlock(&cfs_b->lock);
171
172 cfs_rq->runtime_remaining += amount;
173 - /*
174 - * we may have advanced our local expiration to account for allowed
175 - * spread between our sched_clock and the one on which runtime was
176 - * issued.
177 - */
178 - if (cfs_rq->expires_seq != expires_seq) {
179 - cfs_rq->expires_seq = expires_seq;
180 - cfs_rq->runtime_expires = expires;
181 - }
182
183 return cfs_rq->runtime_remaining > 0;
184 }
185
186 -/*
187 - * Note: This depends on the synchronization provided by sched_clock and the
188 - * fact that rq->clock snapshots this value.
189 - */
190 -static void expire_cfs_rq_runtime(struct cfs_rq *cfs_rq)
191 -{
192 - struct cfs_bandwidth *cfs_b = tg_cfs_bandwidth(cfs_rq->tg);
193 -
194 - /* if the deadline is ahead of our clock, nothing to do */
195 - if (likely((s64)(rq_clock(rq_of(cfs_rq)) - cfs_rq->runtime_expires) < 0))
196 - return;
197 -
198 - if (cfs_rq->runtime_remaining < 0)
199 - return;
200 -
201 - /*
202 - * If the local deadline has passed we have to consider the
203 - * possibility that our sched_clock is 'fast' and the global deadline
204 - * has not truly expired.
205 - *
206 - * Fortunately we can check determine whether this the case by checking
207 - * whether the global deadline(cfs_b->expires_seq) has advanced.
208 - */
209 - if (cfs_rq->expires_seq == cfs_b->expires_seq) {
210 - /* extend local deadline, drift is bounded above by 2 ticks */
211 - cfs_rq->runtime_expires += TICK_NSEC;
212 - } else {
213 - /* global deadline is ahead, expiration has passed */
214 - cfs_rq->runtime_remaining = 0;
215 - }
216 -}
217 -
218 static void __account_cfs_rq_runtime(struct cfs_rq *cfs_rq, u64 delta_exec)
219 {
220 /* dock delta_exec before expiring quota (as it could span periods) */
221 cfs_rq->runtime_remaining -= delta_exec;
222 - expire_cfs_rq_runtime(cfs_rq);
223
224 if (likely(cfs_rq->runtime_remaining > 0))
225 return;
226 @@ -4387,8 +4340,7 @@ void unthrottle_cfs_rq(struct cfs_rq *cf
227 resched_curr(rq);
228 }
229
230 -static u64 distribute_cfs_runtime(struct cfs_bandwidth *cfs_b,
231 - u64 remaining, u64 expires)
232 +static u64 distribute_cfs_runtime(struct cfs_bandwidth *cfs_b, u64 remaining)
233 {
234 struct cfs_rq *cfs_rq;
235 u64 runtime;
236 @@ -4413,7 +4365,6 @@ static u64 distribute_cfs_runtime(struct
237 remaining -= runtime;
238
239 cfs_rq->runtime_remaining += runtime;
240 - cfs_rq->runtime_expires = expires;
241
242 /* we check whether we're throttled above */
243 if (cfs_rq->runtime_remaining > 0)
244 @@ -4438,7 +4389,7 @@ next:
245 */
246 static int do_sched_cfs_period_timer(struct cfs_bandwidth *cfs_b, int overrun)
247 {
248 - u64 runtime, runtime_expires;
249 + u64 runtime;
250 int throttled;
251
252 /* no need to continue the timer with no bandwidth constraint */
253 @@ -4466,8 +4417,6 @@ static int do_sched_cfs_period_timer(str
254 /* account preceding periods in which throttling occurred */
255 cfs_b->nr_throttled += overrun;
256
257 - runtime_expires = cfs_b->runtime_expires;
258 -
259 /*
260 * This check is repeated as we are holding onto the new bandwidth while
261 * we unthrottle. This can potentially race with an unthrottled group
262 @@ -4480,8 +4429,7 @@ static int do_sched_cfs_period_timer(str
263 cfs_b->distribute_running = 1;
264 raw_spin_unlock(&cfs_b->lock);
265 /* we can't nest cfs_b->lock while distributing bandwidth */
266 - runtime = distribute_cfs_runtime(cfs_b, runtime,
267 - runtime_expires);
268 + runtime = distribute_cfs_runtime(cfs_b, runtime);
269 raw_spin_lock(&cfs_b->lock);
270
271 cfs_b->distribute_running = 0;
272 @@ -4558,8 +4506,7 @@ static void __return_cfs_rq_runtime(stru
273 return;
274
275 raw_spin_lock(&cfs_b->lock);
276 - if (cfs_b->quota != RUNTIME_INF &&
277 - cfs_rq->runtime_expires == cfs_b->runtime_expires) {
278 + if (cfs_b->quota != RUNTIME_INF) {
279 cfs_b->runtime += slack_runtime;
280
281 /* we are under rq->lock, defer unthrottling using a timer */
282 @@ -4591,7 +4538,6 @@ static __always_inline void return_cfs_r
283 static void do_sched_cfs_slack_timer(struct cfs_bandwidth *cfs_b)
284 {
285 u64 runtime = 0, slice = sched_cfs_bandwidth_slice();
286 - u64 expires;
287
288 /* confirm we're still not at a refresh boundary */
289 raw_spin_lock(&cfs_b->lock);
290 @@ -4608,7 +4554,6 @@ static void do_sched_cfs_slack_timer(str
291 if (cfs_b->quota != RUNTIME_INF && cfs_b->runtime > slice)
292 runtime = cfs_b->runtime;
293
294 - expires = cfs_b->runtime_expires;
295 if (runtime)
296 cfs_b->distribute_running = 1;
297
298 @@ -4617,11 +4562,10 @@ static void do_sched_cfs_slack_timer(str
299 if (!runtime)
300 return;
301
302 - runtime = distribute_cfs_runtime(cfs_b, runtime, expires);
303 + runtime = distribute_cfs_runtime(cfs_b, runtime);
304
305 raw_spin_lock(&cfs_b->lock);
306 - if (expires == cfs_b->runtime_expires)
307 - cfs_b->runtime -= min(runtime, cfs_b->runtime);
308 + cfs_b->runtime -= min(runtime, cfs_b->runtime);
309 cfs_b->distribute_running = 0;
310 raw_spin_unlock(&cfs_b->lock);
311 }
312 --- a/kernel/sched/sched.h
313 +++ b/kernel/sched/sched.h
314 @@ -280,8 +280,6 @@ struct cfs_bandwidth {
315 ktime_t period;
316 u64 quota, runtime;
317 s64 hierarchical_quota;
318 - u64 runtime_expires;
319 - int expires_seq;
320
321 short idle, period_active;
322 struct hrtimer period_timer, slack_timer;
323 @@ -489,8 +487,6 @@ struct cfs_rq {
324
325 #ifdef CONFIG_CFS_BANDWIDTH
326 int runtime_enabled;
327 - int expires_seq;
328 - u64 runtime_expires;
329 s64 runtime_remaining;
330
331 u64 throttled_clock, throttled_clock_task;