pelt

为什么需要PELT?

之前CFS以每个运行队列为单位跟踪负载;存在几个问题:

  • 一个运行队列存在很多个调度实体,所以无法存在当前的负载来源;
  • 即使工作负载相对稳定的情况下,在rq级别跟踪负载,其值也会产生很大变化。(为什么?)

为了解决以上问题:提出了PETL算法,PELT算法跟踪每个调度实体

如何进行PELT

  • 时间(物理时间,不是虚拟时间)被分成了1024us的序列(以1024us为单位划分)
  • 在每一个1024us的周期中(注意这里是一个周期),一个entity对系统负载的贡献可以根据该实体处于runnable状态(正在CPU上运行或者等待cpu调度运行)的时间进行计算。(负载就是runable的时间占比)
  • 比如:该entity在该周期内,runnable的时间是x,那么对系统负载的贡献就是(x/1024);
  • 一个实体在一个计算周期内(注意是计算周期,有多个1024us)的负载可能会超过1024us(这里以us时间为单位的原因是负载就是runable时间的比值),这是因为我们会累积在过去周期中的负载。当然,对于过去的负载我们在计算的时候需要乘一个衰减因子
  • 如果我们让Li表示在周期pi中该调度实体的对系统负载贡献,那么一个调度实体对系统负荷的总贡献可以表示为:
    L = L0 + L1 * y + L2 * y2 + L3 * y3 + ... + Ln * yn
    y32 = 0.5, y = 0.97857206

如何计算第n个周期的衰减值?

  • 内核提供decay_load()函数用于计算第n个周期的衰减值;
  • 为了避免浮点数运算,采用移位和乘法运算提高计算速度:
    • decay_load(val, n) = val * y^n = val * y^n * 2^32 >> 32 = val * (y^n * 2^32) >> 32
    • 将 (y^n * 2^32) 的值提前计算好放入数组runnable_avg_yN_inv中;

如何计算当前负载贡献

某个时间点的负载u'过了p个周期后的负载值是多少?

  • 假如某个时间点的负载: u = xy^3 + 1024y^2 + 1024*y + x;
  • 相当于分为了四段;
    alt text
  • 过了p个周期之后,每一段负载变为:
    • x*y^3 * y^p
    • 1024*y^2 * y^p
    • 1024*y * y^p
    • x * y^p
  • u的负载是这四段之和,过了p个周期之后,u的负载是:xy^3 * y^p + 1024y^2 * y^p + 1024*y * y^p + x * y^p = u * y^p;
  • 也就是说u过了p个周期后的负载值是: u' * y^p

当前负载计算

           d1          d2           d3
           ^           ^            ^
           |           |            |
         |<->|<----------------->|<--->|
 ... |---x---|------| ... |------|-----x (now)

                           p-1
 u' = (u + d1) y^p + 1024 \Sum y^n + d3 y^0
                           n=1

    = u y^p +					(Step 1)

                     p-1
      d1 y^p + 1024 \Sum y^n + d3 y^0		(Step 2)
                     n=1

首先分为两大段:

  • (Step 1):计算历史负载u对当前负载的贡献u y^p
  • (Step 2):计算经过时间的负载贡献;
    • 假设时间d是经过p(就是完整的周期数+1)个周期(d=d1+d2+d3, p=1+d2/1024),把当前经过的时间分为了三段:
      • d1:d1是离当前时间最远(不完整的)period 的剩余部分
      • d2: 是完整period时间
      • d3: d3是(不完整的)当前 period 的剩余部分

代码实现

数据结构

第n个周期的负载

	if (unlikely(local_n >= LOAD_AVG_PERIOD)) {
		val >>= local_n / LOAD_AVG_PERIOD;	/* 左移一位表示除2,y^32=1/2,也就是左移一位相当于计算y^32;这里计算可以整除多少个32,也就是左移多少位; */
		local_n %= LOAD_AVG_PERIOD;
	}
  • y^32 = 1/2;
  • 左移一位表示除2;
  • 也就是左移一位相当于计算y^32
  • local_n / LOAD_AVG_PERIOD的值,表示可以整除多少个32,也就是左移多少位;
  • mul_u64_u32_shr 计算剩余部分的值; (u64)(((unsigned __int128)a * mul) >> shift);

累计负载计算代码实现

调度实体更新负载贡献

就绪队列更新负载信息

runnable_load_avg和load_avg区别

posted @ 2024-12-25 00:38  _xingxing  阅读(80)  评论(0)    收藏  举报