【WALT】scale_exec_time() 代码详解

详细代码

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;
}

函数核心作用

  1. 频率归一化:将执行时间(delta)转换为基于最大可能频率的等效值

  2. 跨集群标准化:考虑不同 CPU 集群(如 big.LITTLE)的性能差异

  3. 精度处理:保证计算结果的精确性和一致性

代码逻辑

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

为什么要归一化

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

WALT算法引入了一种类似权重的方法,根据CPU的频率(Frequency)和最大周期指令数(efficiency)来对任务的运行时间进行归一化。

1.将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;                   // 时间设为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; // 中断处理使用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); // 记录tracepoint
}

函数核心作用

  1. CPU 周期计数:精确测量任务执行期间消耗的 CPU 周期

  2. 频率估算:为后续的 CPU 频率相关计算提供基础数据

  3. 空闲检测:处理 CPU 空闲状态下的特殊计数情况

  4. IRQ 处理:正确统计中断处理期间的 CPU 周期消耗

2.归一化delta

  • 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的簇来说,频率的计算在 scale_exec_time() 中进行,簇内每个CPU的频率都是一致的,因此一个簇会拥有一个当前频率和一个最大频率,即policy->cpuinfo.max_freq;

        对于单个CPU来说,频率的计算在 scale_exec_time() 中进行,它也会有一个最大频率policy->cpuinfo.max_freq。

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

  • delta *= rq->cluster->exec_scale_factor;

cluster->exec_scale_factor = 1024 * cluster->efficiency/max_possible_efficiency

cluster->efficiency 指运行任务的 CPU 的每周期指令数 (instructions per cycle,IPC)。

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

  • delta >>= 10;

即delta = delta / 1024。

将三句代码一起看,能得出一个等式:

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值