5

【WALT】scale_exec_time() 代码详解 - Cyrusandy

 1 year ago
source link: https://www.cnblogs.com/cyrusandy/p/17533292.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

【WALT】scale_exec_time() 代码详解

代码版本:Linux4.9 android-msm-crosshatch-4.9-android12

static inline u64 scale_exec_time(u64 delta, struct rq *rq)
{
	u32 freq;
	// ⑴ 将 CPU cycles 转换为 CPU 当前频率
	freq = cpu_cycles_to_freq(rq->cc.cycles, rq->cc.time);
	// ⑵ 归一化 delta
	delta = DIV64_U64_ROUNDUP(delta * freq, max_possible_freq);
	delta *= rq->cluster->exec_scale_factor;
	delta >>= 10;

	return delta;
}

代码逻辑:

scale_exec_time() 函数用于给任务的运行时间 delta 进行归一化。

为什么归一化?

EAS 主要针对异构 CPU 架构,如 Arm big.LITTLE,因为这种架构有不同性能和功耗的 CPU 核心,不同 CPU 的最大算力、最大频率等都不同。假定一个任务在当前窗口中运行了 5ms,对不同频率的两个 CPU 来说,5ms 带来的负载是截然不同的。

WALT 算法引入了一种类似权重的方法,根据 CPU 的频率(frequency)和 最大每周期指令数(efficiency)来对任务的运行时间进行归一化。
(注:此处 efficiency 的定义并不确定,在内核文档中出现过这个定义。)

⑴ 将 CPU cycles 转换为 CPU 当前频率

freq = cpu_cycles_to_freq(rq->cc.cycles, rq->cc.time);

static inline u32 cpu_cycles_to_freq(u64 cycles, u64 period)
{
	return div64_u64(cycles, period);
}

在这里 freq = rq->cc.cycles / rq->cc.time。其中,rq->cc.cycles 和 rq->cc.time 在函数 update_task_rq_cpu_cycles() 中更新:

static void
update_task_rq_cpu_cycles(struct task_struct *p, struct rq *rq, int event,
			  u64 wallclock, u64 irqtime)
{
	u64 cur_cycles;
	int cpu = cpu_of(rq);

	lockdep_assert_held(&rq->lock);

	if (!use_cycle_counter) {
		rq->cc.cycles = cpu_cur_freq(cpu);
		rq->cc.time = 1;
		return;
	}

	cur_cycles = read_cycle_counter(cpu, wallclock);

	/*
	 * If current task is idle task and irqtime == 0 CPU was
	 * indeed idle and probably its cycle counter was not
	 * increasing.  We still need estimatied CPU frequency
	 * for IO wait time accounting.  Use the previously
	 * calculated frequency in such a case.
	 */
	if (!is_idle_task(rq->curr) || irqtime) {
		if (unlikely(cur_cycles < p->cpu_cycles))
			rq->cc.cycles = cur_cycles + (U64_MAX - p->cpu_cycles);
		else
			rq->cc.cycles = cur_cycles - p->cpu_cycles;
		rq->cc.cycles = rq->cc.cycles * NSEC_PER_MSEC;

		if (event == IRQ_UPDATE && is_idle_task(p))
			/*
			 * Time between mark_start of idle task and IRQ handler
			 * entry time is CPU cycle counter stall period.
			 * Upon IRQ handler entry sched_account_irqstart()
			 * replenishes idle task's cpu cycle counter so
			 * rq->cc.cycles now represents increased cycles during
			 * IRQ handler rather than time between idle entry and
			 * IRQ exit.  Thus use irqtime as time delta.
			 */
			rq->cc.time = irqtime;
		else
			rq->cc.time = wallclock - p->ravg.mark_start;
		BUG_ON((s64)rq->cc.time < 0);
	}

	p->cpu_cycles = cur_cycles;

	trace_sched_get_task_cpu_cycles(cpu, event, rq->cc.cycles, rq->cc.time, p);
}

⑵ 归一化 delta

  1. delta = DIV64_U64_ROUNDUP(delta * freq, max_possible_freq);
    即 delta = delta * freq/max_possible_freq。

    freq 是当前 CPU 的频率,由 ⑴ 计算而得:freq = rq->cc.cycles / rq->cc.time。

    max_possible_freq 就是 max(policy->cpuinfo.max_freq)。
    policy 可以浅显地认为是簇号,如不同的 policy 指向小核簇、大核簇和超大核:

    • 对于拥有多个 CPU 的簇来说,频率的计算在 sugov_update_shared() 中进行,簇内每个 CPU 的频率都是一致的,因此一个簇会拥有一个当前频率和一个最大频率,即 policy->cpuinfo.max_freq;
    • 对于单个 CPU 来说,频率的计算在 sugov_update_single() 中进行,它也会有一个最大频率 policy->cpuinfo.max_freq。

    在运行该版本内核的 pixel 3xl 中,8 个 CPU 分为小核簇与大核簇,他们的最大频率分别是 381 和 1024。

  2. delta *= rq->cluster->exec_scale_factor;
    cluster->exec_scale_factor = 1024 * cluster->efficiency/max_possible_efficiency

    cluster->efficiency 可能指 运行任务的 CPU 的每周期指令数 (IPC)。

    max_possible_efficiency 可能指 系统中任何 CPU 提供的最大 IPC。
    这个值在设备树中给定,在运行该版本内核的 pixel 3xl 中,小核簇和大核簇的 max_possible_efficiency 分别是 1024 和 1740。

  3. delta >>= 10;
    即 delta = delta / 1024。

将三句代码一起看,能得出一个等式:
delta_s=delta×curr_freqmax_possible_freq×cluster−>efficiencymax_possible_efficiency

点击此处回到 WALT 入口函数 update_task_ravg()


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK