Kernel Scheduler学习之七:关于want affine

 

commitpatchworklkml
63b0e9edceec sched/fair: Beef up wake_widehttps://lore.kernel.org/patchwork/patch/576823https://lkml.org/lkml/2015/7/8/40

理解了这层含义, 那我们 wake_wide 的算法就明晰了. 如下情况我们认为决策是有效的 wake_affine

factor = this_cpu_read(sd_llc_size); 这个因子表示了在当前 NODE 上能够共享 cache 的 CPU 数目(或者说当前sched_domain 中 CPU 的数目), 一个 sched_domain 中, 共享 chache 的 CPU 越多(比如 X86 上一个物理 CPU 上包含多个逻辑 CPU), factor 就越大. 那么在 wake_affine 中的影响就是 wake_wide 返回 0 的概率更大, 那么 wake_affine 的结果有效的概率就更大. 因为有跟多的临近 CPU 可以选择, 这些 CPU 之间 cache 共享有优势.

条件描述
slave < factor即如果 wakee->wakee_flips < factor, 则说明当前进程的唤醒切换不那么频繁, 即使当前进程有 wakee_flips 个 wakee, 当前 sched_domain 也完全能装的下他们.
master < slave * factor即 master/slave < factor, 两个 waker wakee_flips 的比值小于 factor, 那么这种情况下, 进行 wake_affine 的成本可控.

 

  1. Overview
    以下内容摘自https://kernel.taobao.org/2017/08/Reconsidering-the-schedulers-wake_wide-heuristic/

    在多核系统上,内核的调度子系统不仅仅要去决策下一个任务跑啥,还得决策下一个任务跑在哪个CPU上。通常情况下,这种决策被称为“启发式搜索算法”,即一种基于经验法则的最佳实践模式。2015年,有一个关键的“Task-placement heuristic(任务放置启发式搜索算法)”出现了,不过最近的一次讨论让人们开始重新审视它的合理性。

    调度器唤醒这事儿总是在发生:任务经常会等待一个事件(比如:定时器过期,POSIX信号,futex()系统调用等等);这类事件发生的时候,任务被唤醒并继续执行。调度器的任务是找到最适合这个刚被唤醒的任务运行的CPU,所谓核心选得好,任务回家早,选对正确的核心对应用的性能极其关键。一些消息传递型的负载,如果运行在同一个CPU上,性能会更好。比如说 pipetest,它有一对收发任务轮流执行,这对任务永远不用也不会并行执行,这样如果他们的数据都保存在一个CPU的cache里,性能就会好得多。

    不过在现实生活中,应用程序不会有这么密集的通信,而大多数负载也不会这么轮流发消息,所以一般的高并发的应用都是在响应外部输入后发出消息,而发出消息的时机都是随机的。这里一个非常典型的例子是生产者消费者模型,一个主任务唤醒多个从任务。这种类型的负载,如果其任务同时运行于多个CPU上,性能就会好很多。不过现在机器上CPU很多,怎么挑选一个最好的CPU来跑我们的任务变得非常重要。

    另一方面,调度时对CPU的选择也会影响功耗,如果把任务堆在少部分CPU上运行,那么剩下的CPU就可以进入节能模式,如果一个 Socket 上的CPU都空闲了,那么可以更加省电。如果一个节能模式下的空闲CPU被调度上来一个任务,由于CPU从低功耗转变为耗能模式,成本就相应增加了。

    所以综上所述,调度器需要根据唤醒模式来猜测负载是什么类型的,并以此来决策上面的任务是要集中运行于少部分CPU以增加 cache 命中率并节能,还是要全打散来获得更高的CPU利用率。这便是当初 wake_wide() 函数被发明的背景。这个函数是让调度器变得更加错综复杂的众多启发式搜索算法之一,它决定一个刚被å¤醒的任务是应该团结在唤醒它的那个任务所运行的CPU附近,还是应该志在远方,跑到另一个 NUMA 节点上去运行。这里面会有个 tradeoff,把任务打包在一起的方式可以提高本地缓存命中率,做过了却也容易让CPU忙不过来,增加runq的调度延迟。 [编辑] 历史

  2. 代码走读

    在select_task_rq_fair的时候,会记录唤醒关系,如下的record_wakee所示:
    static int
    select_task_rq_fair(struct task_struct *p, int prev_cpu, int sd_flag, int wake_flags)
    {
    	struct sched_domain *tmp, *sd = NULL;
    	int cpu = smp_processor_id();
    	int new_cpu = prev_cpu;
    	int want_affine = 0;
    	int sync = (wake_flags & WF_SYNC) && !(current->flags & PF_EXITING);
    
    	if (sd_flag & SD_BALANCE_WAKE) {
    		record_wakee(p);
    
    		if (sched_energy_enabled()) {
    			new_cpu = find_energy_efficient_cpu(p, prev_cpu);
    			if (new_cpu >= 0)
    				return new_cpu;
    			new_cpu = prev_cpu;
    		}
    
    		want_affine = !wake_wide(p) && !wake_cap(p, cpu, prev_cpu) &&
    			      cpumask_test_cpu(cpu, p->cpus_ptr);
    	}
    static void record_wakee(struct task_struct *p)
    {
    	/*
    	 * Only decay a single time; tasks that have less then 1 wakeup per
    	 * jiffy will not have built up many flips.
    	 */
    	if (time_after(jiffies, current->wakee_flip_decay_ts + HZ)) {//从上一次衰减过之后,到现在超过1s钟,则再次衰减,所以,衰减周期为1s钟,每次衰减1半
    		current->wakee_flips >>= 1;
    		current->wakee_flip_decay_ts = jiffies;
    	}
    
    	if (current->last_wakee != p) {//被当前task唤醒的task发生变化了,则进行计数。
    		current->last_wakee = p;
    		current->wakee_flips++;
    	}
    //所以,wakee_flips表示一段时间被当前task唤醒的不同task数量。值越大,表示较多的task依赖这个task。
    }
    

    如上面的code所示,record_wakee记录了当前task一段时间唤醒其他task的情况。值越大,表示越多的task 依赖当前的task。
    want_affine的判定条件:

    如前overview部分所述,做cpu亲和性选核,为性能考虑,不能让waker与wakee的亲和上来的task超过一定的量使性能下降,例如共簇的cpu有4颗,但是却有8个task相互关联,导致性能下降,即超过了cluster的宽度。同时,在异构的cpu当中,如果大核能力悬殊过大,放在能力更大的cpu上或许可以获得更好的性能,所以,又需要判断能力是否有悬殊。注意这里的亲和性会在当前cpu与task原来所在的cpu之间进行选择。

    		want_affine = !wake_wide(p) && !wake_cap(p, cpu, prev_cpu) &&
    			      cpumask_test_cpu(cpu, p->cpus_ptr);

    wake_wide函数:

    当前 current 正在为 wakeup p, 并为 p 选择一个合适的 CPU. 那么 wake_wide 就用来检查 current 和 p 之间是否适合 wake_affine 所关心的 waker/wakee 模型.

    wake_wide 返回 0, 表示 wake_affine 是有效的. 否则返回 1, 表示这两个进程不适合用 wake_affine.

    
    /*
     * Detect M:N waker/wakee relationships via a switching-frequency heuristic.
     *
     * A waker of many should wake a different task than the one last awakened
     * at a frequency roughly N times higher than one of its wakees.
     *
     * In order to determine whether we should let the load spread vs consolidating
     * to shared cache, we look for a minimum 'flip' frequency of llc_size in one
     * partner, and a factor of lls_size higher frequency in the other.
     *
     * With both conditions met, we can be relatively sure that the relationship is
     * non-monogamous, with partner count exceeding socket size.
     *
     * Waker/wakee being client/server, worker/dispatcher, interrupt source or
     * whatever is irrelevant, spread criteria is apparent partner count exceeds
     * socket size.
     */
    static int wake_wide(struct task_struct *p)
    {
    	unsigned int master = current->wakee_flips;//统计的一段时间依赖当前task的数量
    	unsigned int slave = p->wakee_flips;  //一段时间依赖被唤醒task的数量
    	int factor = this_cpu_read(sd_llc_size);//同簇cpu的数量
    
    	if (master < slave)
    		swap(master, slave);
    	if (slave < factor || master < slave * factor)
    		return 0;
    	return 1;
    }

    wake_affine 在决策的时候, 要参考 wakee_flips
    将 wakee_flips 值大的 wakee 唤醒到临近的 CPU, 可能有利于系统其他进程的唤醒, 同样这也意味着, waker 将面临残酷的竞争.
    此外, 如果 waker 也有一个很高的 wakee_flips, 那意味着多个任务依赖它去唤醒, 然后 1 中造成的 waker 的更高延迟会对这些唤醒造成负面影响, 因此一个高 wakee_flips 的 waker 再去将另外一个高 wakee_flips 的 wakee 唤醒到本地的 CPU 上, 是非常不明智的决策. 因此, 当 waker-> wakee_flips / wakee-> wakee_flips 变得越来越高时, 进行 wake_affine 操作的成本会很高.

由于目前有一些 CPU 都是属于性能异构的 CPU(比如 ARM64 的 big.LITTLE 等), 不同的核 capacity 会差很多. wake_cap 会先看待选择的进程是否。
 


/*
 * Disable WAKE_AFFINE in the case where task @p doesn't fit in the
 * capacity of either the waking CPU @cpu or the previous CPU @prev_cpu.
 *
 * In that case WAKE_AFFINE doesn't make sense and we'll let
 * BALANCE_WAKE sort things out.
 */
static int wake_cap(struct task_struct *p, int cpu, int prev_cpu)
{
	long min_cap, max_cap;

	if (!static_branch_unlikely(&sched_asym_cpucapacity))
		return 0;

	min_cap = min(capacity_orig_of(prev_cpu), capacity_orig_of(cpu));
	max_cap = cpu_rq(cpu)->rd->max_cpu_capacity;

	/* Minimum capacity is close to max, no need to abort wake_affine */
	if (max_cap - min_cap < max_cap >> 3)//大小核能力悬殊不超过12.5%
		return 0;

	/* Bring task utilization in sync with prev_cpu */
	sync_entity_load_avg(&p->se);

	return !task_fits_capacity(p, min_cap);
}

wake_cap表示大小核能力悬殊不超过12.5%。

参考文章:

https://kernel.taobao.org/2017/08/Reconsidering-the-schedulers-wake_wide-heuristic/

https://blog.csdn.net/gatieme/article/details/106315848

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值