Scheduler学习之四:dl_sched_class之选核策略

  1. Overview
    本章内容主要回答如下的问题:
    选核的策略是什么?
     
  2. 选核的策略
    选核策略主要是在select_task_rq_dl中进行的。如下的函数所示:
    static int
    select_task_rq_dl(struct task_struct *p, int cpu, int sd_flag, int flags)
    {
    	struct task_struct *curr;
    	struct rq *rq;
    
    	if (sd_flag != SD_BALANCE_WAKE)
    		goto out;
    
    	rq = cpu_rq(cpu);
    
    	rcu_read_lock();
    	curr = READ_ONCE(rq->curr); /* unlocked access */
    
    	/*
    	 * If we are dealing with a -deadline task, we must
    	 * decide where to wake it up.
    	 * If it has a later deadline and the current task
    	 * on this rq can't move (provided the waking task
    	 * can!) we prefer to send it somewhere else. On the
    	 * other hand, if it has a shorter deadline, we
    	 * try to make it stay here, it might be important.
    	 */
    	if (unlikely(dl_task(curr)) &&//当前cpu上面run的是dl task
    	    (curr->nr_cpus_allowed < 2 ||   //当前task只能跑在一个cpu上面
    	     !dl_entity_preempt(&p->dl, &curr->dl)) &&//当前cpu上面的dl task不可以被抢占
    	    (p->nr_cpus_allowed > 1)) {  //来做选核的task可以跑在多个核上面
    		int target = find_later_rq(p);//选择新的cpu出来
    
    		if (target != -1 &&
    				(dl_time_before(p->dl.deadline,
    					cpu_rq(target)->dl.earliest_dl.curr) ||
    				(cpu_rq(target)->dl.dl_nr_running == 0)))
    			cpu = target;
    	}
    	rcu_read_unlock();
    
    out:
    	return cpu;
    }

    这个funtion表明正常情况task应该选择之前的cpu。但是因为如下的条件满足才会将这个task安排到其他cpu上面。
    a. 原本cpu上的task也是deadline
    b.原本cpu上的task只能跑在一个核上面,或者这个task不能被来选核的task抢占
    c.来选核的task可以跑在多个核上面。
    上面这些条件反映的思想是跑在其他cpu上面,比当前cpu更划算。因为,不用牺牲当前cpu的task的执行时间。
    那么其他cpu是如何选出来的呢?关键的function为:

    static int find_later_rq(struct task_struct *task)
    {
    	struct sched_domain *sd;
    	struct cpumask *later_mask = this_cpu_cpumask_var_ptr(local_cpu_mask_dl);
    	int this_cpu = smp_processor_id();//当前cpu
    	int cpu = task_cpu(task);
    
    	/* Make sure the mask is initialized first */
    	if (unlikely(!later_mask))
    		return -1;
    
    	if (task->nr_cpus_allowed == 1)
    		return -1;
    
    	/*
    	 * We have to consider system topology and task affinity
    	 * first, then we can look for a suitable CPU.
    	 */
    	if (!cpudl_find(&task_rq(task)->rd->cpudl, task, later_mask))//寻找可选的cpu
    
    		return -1;
    
    	/*
    	 * If we are here, some targets have been found, including
    	 * the most suitable which is, among the runqueues where the
    	 * current tasks have later deadlines than the task's one, the
    	 * rq with the latest possible one.
    	 *
    	 * Now we check how well this matches with task's
    	 * affinity and system topology.
    	 *
    	 * The last CPU where the task run is our first
    	 * guess, since it is most likely cache-hot there.
    	 */
    	if (cpumask_test_cpu(cpu, later_mask))//如果task原来所在的cpu在其中的话,就选其原来所在的cpu
    		return cpu;
    	/*
    	 * Check if this_cpu is to be skipped (i.e., it is
    	 * not in the mask) or not.
    	 */
    	if (!cpumask_test_cpu(this_cpu, later_mask))//判断当前cpu是否可以skip
    		this_cpu = -1;
    
    	rcu_read_lock();
    	for_each_domain(cpu, sd) {
    		if (sd->flags & SD_WAKE_AFFINE) {//如果所处的schedule_domain设置了affine属性,则尝试选择当前cpu,这比较符合sd_wake_affine的本义。sd_wake_affine本意就是Wake task to waking CPU
    			int best_cpu;
    
    			/*
    			 * If possible, preempting this_cpu is
    			 * cheaper than migrating.
    			 */
    			if (this_cpu != -1 &&
    			    cpumask_test_cpu(this_cpu, sched_domain_span(sd))) {//奇怪,什么情况会打到当前cpu不在所属的domain中的现象呢?
    				rcu_read_unlock();
    				return this_cpu;
    			}
    
    			best_cpu = cpumask_first_and(later_mask,
    							sched_domain_span(sd));
    //最后的选择是sched_domin与候选cpu中的交集的第1个cpu。
    			/*
    			 * Last chance: if a CPU being in both later_mask
    			 * and current sd span is valid, that becomes our
    			 * choice. Of course, the latest possible CPU is
    			 * already under consideration through later_mask.
    			 */
    			if (best_cpu < nr_cpu_ids) {
    				rcu_read_unlock();
    				return best_cpu;
    			}
    		}
    	}
    	rcu_read_unlock();
    
    	/*
    	 * At this point, all our guesses failed, we just return
    	 * 'something', and let the caller sort the things out.
    	 */
    	if (this_cpu != -1) //还没选到就选择当前cpu
    		return this_cpu;
    
    	cpu = cpumask_any(later_mask);//随便找一个cpu吧。
    	if (cpu < nr_cpu_ids)
    		return cpu;
    
    	return -1;
    }


    候选cpu是怎么选出来的呢?

    /*
     * cpudl_find - find the best (later-dl) CPU in the system
     * @cp: the cpudl max-heap context
     * @p: the task
     * @later_mask: a mask to fill in with the selected CPUs (or NULL)
     *
     * Returns: int - CPUs were found
     */
    int cpudl_find(struct cpudl *cp, struct task_struct *p,
    	       struct cpumask *later_mask)
    {
    	const struct sched_dl_entity *dl_se = &p->dl;
    
    	if (later_mask &&
    	    cpumask_and(later_mask, cp->free_cpus, p->cpus_ptr)) { //优先找free的cpu出来。
    		return 1;
    	} else {   //如果没有free cpu出来
    		int best_cpu = cpudl_maximum(cp); //寻找每颗cpu上最早deadline值最大的那颗cpu,即所有cpu中最早deadline最晚的cpu
    
    		WARN_ON(best_cpu != -1 && !cpu_present(best_cpu));
    
    		if (cpumask_test_cpu(best_cpu, p->cpus_ptr) &&
    		    dl_time_before(dl_se->deadline, cp->elements[0].dl)) { //se要抢别人的cpu,要满足EDF原则,即最早deadline 优先的原则。
    			if (later_mask)
    				cpumask_set_cpu(best_cpu, later_mask);
    
    			return 1;
    		}
    	}
    	return 0;
    }
    

    所以,候选的CPU主要有两种可能性:
    a.空闲的CPU
    b.如果没有空闲,所有CPU 中deadline最晚且比当前task的deadline还晚的CPU,这样也是为了满足EDF的原则。
    那么如何记录free CPU,如何快速找到最早deadline且最晚的CPU的呢?
    主要是cpudeadline.c文件中进行的。这里使用到了堆化技术。关于堆化技术参考:https://blog.csdn.net/qq508618087/article/details/53362999
    根据堆的性质, 只要保证部分有序即可, 即根节点大于左右节点的值. 将数组抽象为一个完全二叉树, 所以只要从最后一个非叶子节点向前遍历每一个节点即可. 如果当前节点比左右子树节点都大, 则已经是一个最大堆, 否则将当前节点与左右节点较大的一个交换, 并且交换过之后依然要递归的查看子节点是否满足堆的性质, 不满足再往下调整. 如此即可完成数组的堆化.
    ————————————————
    版权声明:本文为CSDN博主「小榕流光」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/qq508618087/article/details/53362999
    一开始的时候:

    init_rootdomain函数中初始化了一个类似全局变量的成员变量:

    struct root_domain {
    	......
    	struct cpudl		cpudl;
    }
    
    static int init_rootdomain(struct root_domain *rd)
    {
    ......
    	if (cpudl_init(&rd->cpudl) != 0)
    		goto free_rto_mask;
    }
    
    
    /*
     * cpudl_init - initialize the cpudl structure
     * @cp: the cpudl max-heap context
     */
    int cpudl_init(struct cpudl *cp)
    {
    	int i;
    
    	raw_spin_lock_init(&cp->lock);
    	cp->size = 0;
    
    	cp->elements = kcalloc(nr_cpu_ids,
    			       sizeof(struct cpudl_item),
    			       GFP_KERNEL); //有多少个CPU就多少个元素.而elements就是后面管理堆化的数组
    	if (!cp->elements)
    		return -ENOMEM;
    
    	if (!zalloc_cpumask_var(&cp->free_cpus, GFP_KERNEL)) {
    		kfree(cp->elements);
    		return -ENOMEM;
    	}
    
    	for_each_possible_cpu(i)
    		cp->elements[i].idx = IDX_INVALID;
    
    	return 0;
    }
    
    cpudl的结构体如下所示:
    struct cpudl_item {
    	u64			dl;
    	int			cpu;
    	int			idx;
    };
    
    struct cpudl {
    	raw_spinlock_t		lock;
    	int			size;
    	cpumask_var_t		free_cpus;
    	struct cpudl_item	*elements;
    };

    怎样确保来设定的deadline就是这个CPU最早deadline的呢?
    猜测也应该在enqueue与dequeue的设定.事实上确实如此:

     

    void cpudl_set(struct cpudl *cp, int cpu, u64 dl)
    {
    	int old_idx;
    	unsigned long flags;
    
    	WARN_ON(!cpu_present(cpu));
    
    	raw_spin_lock_irqsave(&cp->lock, flags);
    
    	old_idx = cp->elements[cpu].idx;
    	if (old_idx == IDX_INVALID) { //加入一个新元素
    		int new_idx = cp->size++;
    
    		cp->elements[new_idx].dl = dl;
    		cp->elements[new_idx].cpu = cpu;
    		cp->elements[cpu].idx = new_idx;
    		cpudl_heapify_up(cp, new_idx); //堆化操作.
    		cpumask_clear_cpu(cpu, cp->free_cpus);
    	} else {
    		cp->elements[old_idx].dl = dl; //修改一个元素
    		cpudl_heapify(cp, old_idx);  //修改之后,仍然保持数组堆的性质.
    	}
    关于堆化可以参考:https://blog.csdn.net/qq508618087/article/details/53362999
    

    根据堆的性质,第1个元素是整个数组中元素值最大的元素,所以:
     

    static inline int cpudl_maximum(struct cpudl *cp)
    {
    	return cp->elements[0].cpu;
    }

    综上,deadline是通过堆化管理来提供所有cpu中最早deadline最晚CPU的.

  3. 总结
    deadline为一个task选核时,优先选择这个task之前所在的cpu,但是如果之前所在的cpu,因为某些条件不能给当前task使用时,会再优先选择free的cpu,如果没有free cpu则选择一个最早deadline最晚的cpu并且deadline比较自己还晚的cpu进行抢。如果没有满足条件的cpu,就还是放到当前的CPU当中,只不过会进行排队。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这段话是一个配置文件,用于指定训练神经网络模型的超参数和数据集参数。具体解释如下: - setup: selflabel:表示使用自标签技术进行训练。 - confidence_threshold: 0.99:表示置信度阈值为0.99。 - use_ema: True,ema_alpha: 0.999:表示使用指数移动平均(EMA)方法进行训练,并设置EMA的衰减系数为0.999。 - criterion: confidence-cross-entropy,criterion_kwargs: apply_class_balancing: False:表示使用置信度交叉熵损失函数进行训练,并关闭类别平衡。 - backbone: resnet50,num_heads: 1:表示使用ResNet-50作为模型骨架,并设置模型头数为1。 - train_db_name: imagenet_50,val_db_name: imagenet_50,num_classes: 50:表示使用ImageNet-50数据集进行训练和验证,并共有50个类别。 - augmentation_strategy: ours,augmentation_kwargs: crop_size: 224,normalize: mean: [0.485, 0.456, 0.406] std: [0.229, 0.224, 0.225],num_strong_augs: 4,cutout_kwargs: n_holes: 1 length: 75 random: True:表示使用作者自己的数据增强策略进行训练,并设置裁剪尺寸为224、归一化参数为给定值、强增强次数为4、cutout参数为1个洞、洞的大小为75,并随机选择位置。 - transformation_kwargs: crop_size: 224,normalize: mean: [0.485, 0.456, 0.406] std: [0.229, 0.224, 0.225]:表示在验证时使用裁剪尺寸为224、归一化参数为给定值的数据增强策略。 - optimizer: sgd,optimizer_kwargs: lr: 0.03,weight_decay: 0.0,nesterov: False,momentum: 0.9:表示使用随机梯度下降(SGD)优化器进行训练,并设置学习率为0.03、权重衰减为0.0、是否使用Nesterov动量为False、动量系数为0.9。 - epochs: 25,batch_size: 512,num_workers: 16:表示总共训练25个epochs,每个batch的大小为512,使用16个worker进行数据加载。最后,scheduler: constant表示使用恒定的学习率调度程序,即不会随着训练过程中的epoch数量而改变。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值