一次cfs组调度不公平引起的负载不均衡分析及cfs组调度深入探索(五)

本文详细解释了PATCH-2中的load_sum和load_avg在调度实体和cfs_rq之间的计算过程,以及在广播负载期间的更新策略,确保了平均负载的正确性和同步性,避免了load_sum=0而load_avg≠0的异常情况。
摘要由CSDN通过智能技术生成

关于 PATCH-2 中 load_sum = 0,load_avg 可能不为零的测试与验证与平均负载计算的总结

根据之前分析,可以得到下面几个前提条件:
1)对于 cfs_rq 的 load_sum 来源于任务节点下下所有调度实体 se 的 load_sum * se->load.weight 之和,同时由调度实体 attch/deattch 附加或者移除 load_sum。同时在对 cfs_rq 进行 pelt 更新时,也是根据当前 cfs_rq->load.weight * contrib 累加得来。其中 contrib 对于 cfs_rq 是当前周期的时间片 delta。

cfs_rq load_sum += weight * contrib(contrib = delta)

2)对于 cfs_rq 的 load_avg 同上,由调度实体 attch/deattch 附加或者移除 load_avg。同时在对 cfs_rq 进行 pelt 更新时,load_avg = load_sum / divider。(cfs_rq 的 load_sum 等于 cfs_rq->load.weight * contrib)。

cfs_rq load_avg = load_sum / divider(load_sum = \Sum weight * delta)

从1)2)可知道当任务满载运行则负载 load_avg 越接近其权重值。
3)对于调度实体 se,如果 se 是 task,load_sum = 1 * contrib = delta,即任务的 load_sum 等于时间片的累积。

se load_sum += delta

4)对于调度实体 se,如果 se 是 group,load_sum = cfs_rq->weight * contrib,即任务组 se 的 load_sum 是时间片累计乘以对应 cfs_rq 权重。

se load_avg = load_sum * weight / divider

5)对于调度实体 se。如果 se 是 group,se 的权重等于:

se->load.weight = max(tg->share, tg->share * cfs_rq->avg.load_avg / tg->load_avg + cfs_rq->avg.load_avg)

接下来看广播负载期间 cfs_rq 和 se 的 load_sum 和 load_avg 计算:

static inline void
update_tg_cfs_runnable(struct cfs_rq *cfs_rq, struct sched_entity *se, struct cfs_rq *gcfs_rq)
{
	long delta_avg, running_sum, runnable_sum = gcfs_rq->prop_runnable_sum; ----------------------1unsigned long runnable_load_avg, load_avg;
	u64 runnable_load_sum, load_sum = 0;
	s64 delta_sum;

	if (!runnable_sum)
		return;

	gcfs_rq->prop_runnable_sum = 0;

	if (runnable_sum >= 0) { --------------------------------------------------------------------2/*
		 * Add runnable; clip at LOAD_AVG_MAX. Reflects that until
		 * the CPU is saturated running == runnable.
		 */
		runnable_sum += se->avg.load_sum;
		runnable_sum = min(runnable_sum, (long)LOAD_AVG_MAX);
	} else { ------------------------------------------------------------------------------------3/*
		 * Estimate the new unweighted runnable_sum of the gcfs_rq by
		 * assuming all tasks are equally runnable.
		 */
		if (scale_load_down(gcfs_rq->load.weight)) {
			load_sum = div_s64(gcfs_rq->avg.load_sum,
				scale_load_down(gcfs_rq->load.weight)); -----------------------------------------4}

		/* But make sure to not inflate se's runnable */
		runnable_sum = min(se->avg.load_sum, load_sum);
	}

	/*
	 * runnable_sum can't be lower than running_sum
	 * As running sum is scale with CPU capacity wehreas the runnable sum
	 * is not we rescale running_sum 1st
	 */
	running_sum = se->avg.util_sum /
		arch_scale_cpu_capacity(NULL, cpu_of(rq_of(cfs_rq)));
	runnable_sum = max(runnable_sum, running_sum);  --------------------------------------------5)

	load_sum = (s64)se_weight(se) * runnable_sum; ----------------------------------------------6)
	load_avg = div_s64(load_sum, LOAD_AVG_MAX);

	delta_sum = load_sum - (s64)se_weight(se) * se->avg.load_sum; ------------------------------7)
	delta_avg = load_avg - se->avg.load_avg; ---------------------------------------------------8)

	se->avg.load_sum = runnable_sum; -----------------------------------------------------------9)
	se->avg.load_avg = load_avg; ---------------------------------------------------------------10add_positive(&cfs_rq->avg.load_avg, delta_avg); --------------------------------------------11add_positive(&cfs_rq->avg.load_sum, delta_sum); --------------------------------------------12)

	runnable_load_sum = (s64)se_runnable(se) * runnable_sum;
	runnable_load_avg = div_s64(runnable_load_sum, LOAD_AVG_MAX);
	delta_sum = runnable_load_sum - se_weight(se) * se->avg.runnable_load_sum;
	delta_avg = runnable_load_avg - se->avg.runnable_load_avg;

	se->avg.runnable_load_sum = runnable_sum;
	se->avg.runnable_load_avg = runnable_load_avg;

	if (se->on_rq) {
		add_positive(&cfs_rq->avg.runnable_load_avg, delta_avg);
		add_positive(&cfs_rq->avg.runnable_load_sum, delta_sum);
	}
}

(1)其中 gcfs_rq->prop_runnable_sum 来自于下一层某个 se 移除或添加到 cfs_rq 时的 load_sum,也是在这一层需要广播到 cfs_rq 的 load_sum。
(2)如果是 attach,那么此时将当前级别的 se load_sum 和广播的 load_sum 相加得到一个附加后的负载总和,同时根据 pelt 算法可知,单个 se 的 load_sum 最大为 LOAD_AVG_MAX,我们不能超过该值。
(3)如果是 deattach,那么此时则是在当前级别移除相应的 load_sum,移除多少呢?首先判断来源处的 cfs_rq 是否还有 weight 存在。因为对于 cfs_rq 的权重来自于节点下所有 se 的累加,所以如果权重为零说明没有任务了那么此时只需要将其对应的 se 标记为 runnable_sum = 0,说明 se 没有任何负载总和。那么如果有权重,我们就直接取它当前 load_sum 为我们 se 的 load_sum 即可,保证了 cfs_rq 和 se load_sum 同步,通过上面 4)可知, cfs_rq 的 load_sum 是权重与时间片累积的相乘所以这里除以权重得到 se 真实的 load_sum。
(5)如果瞬时利用率存在我们要用刚才的 runnable_sum 和 running_sum 取最大值,原因为对于正在运行的任务顺势负载我们不能直接移除,其中 running_sum 计算方式有

util_sum = contrib * 1 << 10(其中 1 << 10 是默认设置, contrib 同 load_sum 一致来源于 delta)

所以这里直接使用 util_sum / 1 << 10 得到 running_sum ,使其负载总和范围在 0 到 LOAD_AVG_MAX 之间。
(6)用于 cfs_rq 的 load_sum 计算,所以这里需要将 se 的 load_sum 乘以权重得到 cfs_rq 的 load_sum。
(7)新的 runable_sum 减去原来 se 上的 load_sum *weight 则可以得到需要在对应 cfs_rq 上需要移除或者添加的 load_sum。
(8)同理得到 load_sum。
(9)(10)将新算出来的 load_sum 和 load_avg 设置到 se 上。
(11)(12)根据(7)(8)计算出来的差值从 cfs_rq 上添加或移除相应的值。

下面是一组正常运行测试的负载打印:

[  718.082948] xxxxx: old ffff98d18177e600 cpu 15:load_sum 861433 load_avg 18 tg_load_avg_contrib 5 delta_sum 47253504 delta_avg 1006 load_avg 1024
[  718.086925] xxxxx: new ffff98d18177e600 cpu 15:load_sum 48114937 load_avg 1024 tg_load_avg_contrib 5
[  719.091621] xxxxx: old ffff98d18177e600 cpu 15:load_sum 618269 load_avg 13 tg_load_avg_contrib 0 delta_sum -617472 delta_avg -13 load_avg 0
[  719.096940] xxxxx: new ffff98d18177e600 cpu 15:load_sum 797 load_avg 0 tg_load_avg_contrib 0

可以看到其中一组,在更新负载前 cfs_rq load_sum 等于 618269 ,load_avg 13,按照上述上述公式 618269 / 47742 =12 大致是等于 13 的。符合pelt 公式。
广播负载后该 cfs_rq load_sum 等于 797,load_avg 为 0在,其差值正是(7)(8)计算得来。
下面是一组触发 bug 的负载打印:

[   10.764182] msleep.sh (185) used greatest stack depth: 12960 bytes left
[   10.765054] xxxxx: old ffff9636823d6200 cpu 6:load_sum 275565 load_avg 5 tg_load_avg_contrib 5 delta_sum -275456 delta_avg -5 load_avg 0
[   10.766152] xxxxx: old ffff9636823d5e00 cpu 5:load_sum 2146304 load_avg 45 tg_load_avg_contrib 44 delta_sum -2146304 delta_avg -45 load_avg 0
[   10.772207] xxxxx: new ffff9636823d6200 cpu 6:load_sum 109 load_avg 0 tg_load_avg_contrib 5
[   10.776383] xxxxx: new ffff9636823d5e00 cpu 5:load_sum 0 load_avg 0 tg_load_avg_contrib 44

===================================Wed Mar 22 16:28:26 UTC 2023============================================
cfs_rq[2]:/zy_test_l1_32/zy_test_l2_32
  .tg_load_avg_contrib           : 1012
  .tg_load_avg                   : 1012
cfs_rq[2]:/zy_test_l1_32
  .tg_load_avg_contrib           : 32
  .tg_load_avg                   : 76
^C*************************
     do usetup, exit
*************************

可以看到被遗留的负载是 76 -32 = 44,上述打印中 cpu 5 上 load_sum 从 109 变为 0 则按照 patch 描述触发移除 cfs_rq list 导致其负载无法移除,tg_load_avg_contrib = 44 符合遗留负载量。
通过大量测试发现,没有 load_sum = 0 ,load_avg != 0 的情况,按照实际 update_tg_cfs_runnable 执行逻辑也可以看到最多只会出现 load_sum > 0 ,load_avg = 0 的情况。这种时候按照 load_avg = weight * load_sum / divider 计算的话,当 load_sum * weight < LOAD_AVG_MAX 也是符合 load_avg = 0 的情况,不会出现 load_sum = 0,load_avg != 0 的情况。按照 patch 的方式可以保证当 load_avg = 0 时,load_sum 也会一定等于 0,那么此时可以将 cfs_rq list 移除不参与负载更新。因为如果 load_sum > 0, load_avg = 0 时,在下一次复杂更新时不会做任何负载贡献,此时这个更新操作没有意义,通过同步 load_avg 和 load_sum 可以少去不必要的更新。

  • 17
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值