规避 CFS 的非公平性问题(睡眠补偿等等),99年发表论文,15年heracles论文重新对 bvt 做了改进,从论文作者的名字,我扒到了对应的源码,这哥们把源码放到gist上了
1. cfs 睡眠补偿机制
在讲bvt之前,有必要先介绍一下 cfs 的睡眠补偿机制
cfs 调度器的目标是公平,cfs 希望每个进程得到调度的机会是一样的,这个“机会”是用 vruntime 来衡量的
但是如果一个进程一直在睡眠,那么它的 vruntime 是非常小的,当睡眠中的进程被唤醒时,基于 CFS 的调度逻辑,会一直持续运行当前进程,直到 vruntime 不是最小的时候,才会选择下一个进程来调度。
内核为了解决 sleep 进程获得过长时间的问题,增加了一个阈值限制,当进程被唤醒时,取当前运行队列的最小vruntime,并 + 上一个偏移量,这个偏移量默认是 1/2 个调度周期,12ms
static void place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int initial) { u64 vruntime = cfs_rq->min_vruntime; /* * The 'current' period is already promised to the current tasks, * however the extra weight of the new task will slow them down a * little, place the new task so that it fits in the slot that * stays open at the end. */ if (initial && sched_feat(START_DEBIT)) vruntime += sched_vslice(cfs_rq, se); /* sleeps up to a single latency don't count. */ if (!initial) { unsigned long thresh = sysctl_sched_latency; /* * Halve their sleep time's effect, to allow * for a gentler effect of sleepers: */ if (sched_feat(GENTLE_FAIR_SLEEPERS)) thresh >>= 1; vruntime -= thresh; } /* ensure we never gain time by being placed backwards. */ se->vruntime = max_vruntime(se->vruntime, vruntime); }
也就是说,当进程被唤醒之后,至少会得到 4ms 完整的运行时间(因为12ms > 最小保证运行时间,这是cfs公平性的另外一个机制),这个时间不可被中断,除非进程主动让出 CPU
2. bvt 原理
从源码上来看,实现非常简单(这个可能和原来论文的设计应该不一样了),bvt 并不改变 cfs 的公平性,它只是修改了一下进程唤醒时的睡眠补偿
这个 patch 增加了一个新的 cgroup 接口:bvt_warp_ns
看下接口定义:
+ * If the BVT_PLACEMENT scheduler feature is enabled, waking BVT tasks + * are placed differently from CFS tasks when they wakeup. Rather + * than being placed some large factor (i.e. sched_latency >> 1) + * before min_vruntime (which gives waking tasks an unfair advantage + * in preempting currently runng tasks), they are placed + * sched_bvt_place_epsilon nanoseconds relative to min_vruntime. If + * you really want a BVT task to preempt currently running tasks, it + * should have a greater "warp" value than the current running task.
简单来说:这个值越大越好
+#ifdef CONFIG_CFS_BVT +static inline void update_effective_vruntime(struct sched_entity *se) +{ + s64 warp; + struct task_group *tg; + + if (entity_is_task(se)) { + se->effective_vruntime = se->vruntime; + return; + } + + tg = se->my_q->tg; + warp = tg->bvt_warp_ns; + + /* FIXME: Should we calc_delta_fair on warp_ns? */ + se->effective_vruntime = se->vruntime - warp; + se->is_warped = warp ? 1 : 0; +} +#endif /* CONFIG_CFS_BVT */
每次 update_effective_vruntime 的时候,进程的 vruntime 就会少减少 bvt_warp_ns,因为 cfs 队列是个红黑树,每次调度的时候选 vruntime 最小的进程来调度。因此 vruntime 越小,得到调度的机会就越大
update_effective_vruntime 在进程进出 cfs 队列,进程唤醒,抢占等很多地方都会调用到,具体可以看 patch
3. bvt 测试
不过,在实际测试的过程中,我们发现 bvt 对 latency sensitive 程序并没有太多优化
hackbench 是一个用来衡量调度器的性能的开源benchmark。它的原理是启动 N 个 reader/write进程或线程对,通过 IPC(socket 或者 pipe) 进行并发的读写,来测试调度的延迟,响应速度等。
case 1:在同一个cpu上启动一个cpu消耗性程序和hackbench,他们处于同一个cpu cgroup下,观察hackbench的运行时间
case 2:在同一个cpu上启动一个cpu消耗性程序和hackbench,eatcpu不受BVT影响,hackbench受BVT的影响,观察hackbench的运行时间
执行时间
|
case 1
|
case 2(bvt =40ms)
|
case 2(bvt =1ms)
|
case 2(bvt =0.1ms)
|
1
|
2.585
|
5.045
|
5.415
|
5.534
|
2
|
2.656
|
5.329
|
5.330
|
|
3
|
2.727
|
5.467
|
5.438
|
|
4
|
2.652
|
|||
9
|
2.634
|
但是实际测试发现,开了bvt性能反而更差了
分析了一下,原因是 bvt 中的sysctl_sched_bvt_place_epsilon,使得睡眠被唤醒的进程得不到补偿,导致hackbench测试性能急剧下降。
在使用sysctl_sched_bvt_place_epsilon计算新的vruntime,使用之前已经被补偿一个运行周期的vruntime,而不是当前运行队列的最小vruntime
测试结果:
执行时间
|
case 1
|
case 2(bvt =4ms)
|
case 2(bvt =40ms)
|
case 2(bvt =0.1ms)
|
1
|
2.429
|
2.441
|
2.404
|
2.417
|
2
|
2.451
|
2.436
|
2.426
|
|
3
|
2.408
|
2.495
|
2.317
|
|
4
|
2.407
|
2.354
|
2.403
|
|
5
|
2.399
|
2.400
|
||
6
|
2.322
|
2.396
|
||
7
|
2.399
|
2.360
|
||
8
|
2.416
|
2.449
|
||
9
|
2.339
|
2.395
|
结论:
在使用如上的patch后,修复了BVT的负面影响,但测试看BVT对任务延迟没有什么影响