Linux-scheduler之负载均衡(一)

news2024/12/24 0:36:06

一、如何衡量CPU的负载

衡量CPU负载

简单衡量

C P U 负 载 = 就 绪 队 列 的 总 权 重 CPU负载 = 就绪队列的总权重 CPU=

量化负载

C P U 负 载 = ( 采 样 期 间 累 计 运 行 时 间 / 采 样 总 时 间 ) ∗ 就 绪 队 列 总 权 重 CPU负载 = (采样期间累计运行时间/采样总时间)*就绪队列总权重 CPU=(/)

工作负载

C P U 工 作 负 载 = 采 样 期 间 累 计 运 行 时 间 ∗ 就 绪 队 列 总 权 重 CPU工作负载 = 采样期间累计运行时间*就绪队列总权重 CPU=

历史衰减工作负载计算

上述中量化负载是把采样总时间段内的所有工作负载等同化,这是不准确的;比如采样时间内,分成若干个period,每个period中运行时间都不一样(也即CPU占用率不一样),随着时间推移,过去的period中负载对未来的负载估算/计算的贡献是需要减弱的

把1ms(准确说是1024us,方便移位操作)的时间跨度作为一个计算周期period,简称PI。一个entity在PI周期内对工作负载的贡献主要与两个因素有关:

  • 进程的权重

  • PI内可运行时间的衰减累计值(这个我理解为:计算某个PI周期时的负载贡献,需要计算上该周期距离最新周期的累计衰减,也就是该周期内实际运行时间x衰减系数

历史累计衰减工作负载计算公式
d e c a y _ s u m _ l o a d = d e c a y _ s u m _ t i m e ∗ w e i g h t             ( 1.4 ) decay\_sum\_load = decay\_sum\_time * weight\ \ \ \ \ \ \ \ \ \ \ (1.4) decay_sum_load=decay_sum_timeweight           (1.4)
decay_sum_load: 累计工作总负载

decay_sum_time: 累计衰减总时间,这里是计算过去若干个PI周期内的可运行时间的衰减累计值

weight:权重

上述计算累计负载的意义是:把该进程在过去的若干周期内的工作负载贡献都考虑进来

为何需要这样考虑呢?进程的行为并不是统一的,有些进程是长时间睡眠后突然占用CPU,所以如果只以此时该进程的负载来计算未来该进程可能贡献的负载就相当不准确,所以需要考虑历史累计工作负载

说到底,还是为了公平调度,完美的调度算法需要一个能够预知未来的水晶球:只有当内核准确地推测出每个进程对系统的需求,她才能最佳地完成调度任务。所以做这么多运算,就是为更好的预测未来每个进程对CPU的需求。只是预测罢了

如果我们让Li表示在周期PI中该调度实体的对系统负载贡献,那么一个调度实体对系统负荷的总贡献可以表示为:
L = L 0 + L 1 ∗ y + L 2 ∗ y 2 + L 3 ∗ y 3 + …         ( 1.5 ) L = L0 + L1*y + L2*y2 + L3*y3 + … \ \ \ \ \ \ \ (1.5) L=L0+L1y+L2y2+L3y3+       (1.5)
其中y是衰减因子,3.8内核后已约定好y的32次方约等于0.5,也就是32个周期PI后,Li贡献度降一半。通过上面的公式可以看出:

(1) 调度实体对系统负荷的贡献值是一个序列之和组成

(2) 最近的负荷值拥有最大的权重

(3) 过去的负荷也会被累计,但是是以递减的方式来影响负载计算。

量化负载计算

公式(1.4)中计算的工作负载直接用来比较两个进程的负载大小是不合理的。原因:公式(1.4)中,权重weight是个容易量化的值,而累计衰减时间是很难量化的。假设进程A和进程B的权重相同,A运行了几天几夜,B才运行了几分钟,如果按照公式(1.4)计算,那么进程A的累计工作负载大于B,但这可能计算的没有意义

所以,需要量化负载(其实就是计算某个采样时间段内的工作负载占比)
d e c a y _ a v g _ l o a d = ( d e c a y _ s u m _ r u n n a b l e _ t i m e / d e c a y _ s u m _ p e r i o d _ t i m e ) ∗ w e i g h t               ( 1.6 ) decay\_avg\_load = (decay\_sum\_runnable\_time/decay\_sum\_period\_time) * weight \ \ \ \ \ \ \ \ \ \ \ \ \ (1.6) decay_avg_load=(decay_sum_runnable_time/decay_sum_period_time)weight             (1.6)
其中:

decay_avg_load: 量化负载

decay_sum_runnable_time: 就绪队列或调度实体在可运行状态下的所有历史累计衰减时间

decay_sum_period_time: 就绪队列或调度实体在所有的采样周期里全部时间的累加衰减时间。通常从进程开始执行时就计算和累计该值

weight: 就绪队列或调度实体的权重

Linux中,CPU对应的就绪队列中所有进程的量化负载累加起来得到CPU总负载。SMP负载均衡算法使用量化负载来比较两个CPU的负载是否均衡。

实际算力

处理器的算力:处理器最大的处理能力。SMP架构下,CPU计算能力相同。ARM的大小核架构下,CPU计算能力就不同。

量化计算能力:如果最强大的CPU的量化计算能力设定为1024,那么其他计算力较弱的CPU的量化计算能力就小于1024。CPU的量化计算能力是通过设备树或者BIOS等方式提供给Linux内核,rq数据结构体中用cpu_capacity_orig来描述这个量化计算能力。

实际算力:
u t i l _ a v g = ( d e c a y _ s u m _ r u n n i n g _ t i m e / d e c a y _ s u m _ p e r i o d _ t i m e ) ∗ c p u _ c a p a c i t y util\_avg = (decay\_sum\_running\_time/decay\_sum\_period\_time) * cpu\_capacity util_avg=(decay_sum_running_time/decay_sum_period_time)cpu_capacity
其中,cpu_capacity是处理器的额定算力,默认值是1024(SCHED_CAPACITY_SCALE)

二、CFS中的负载计算

sched_avg结构体

struct sched_avg {
    u64                last_update_time;
    u64                load_sum;
    u64                runnable_sum;
    u32                util_sum;
    u32                period_contrib;
    unsigned long            load_avg;
    unsigned long            runnable_avg;
    unsigned long            util_avg;
    struct util_est            util_est;
} ____cacheline_aligned;
成员类型针对sched_entity针对cfs_rq
last_update_timeu64上一次更新的时间点,用于计算时间间隔,这好像是ns单位
load_sumu64对于sched_entity: 进程在就绪队列里的可运行状态下的累计衰减总时间(decay_sum_time),计算的是时间值对于cfs_rq:调度队列中所有进程的累计工作总负载(decay_sum_load)
runnable_sumu64对于sched_entity:进程在就绪队列里的可运行状态下的累计衰减总时间(decay_sum_time);对于cfs_rq:调度队列中所有可运行状态下进程的累计工作总负载(decay_sum_load)
util_sumu32对于sched_entity:正在运行状态下的累计衰减总时间(decay_sum_time)。(使用cfs_rq->curr == se来判断进程是否正在运行);对于cfs_rq:就绪队列中所有处于运行状态进程的累计衰减总时间(decay_sum_time)。只要就绪队列里有正在运行的进程,它就会累加
period_contribu32存放上一次时间采样时,不能凑成一个周期(1024us)的剩余的时间
load_avgunsigned long对于sched_entity:可运行状态下的量化负载(decay_avg_load)。负载均衡中,使用该成员来衡量一个进程的负载贡献值,如衡量迁移进程的负载量对于cfs_rq:就绪队列中总的量化负载
runnable_avgunsigned long对于sched_entity:可运行状态下的量化负载,等同load_avg;对于cfs_rq:就绪队列里所有课运行状态下进程的总量化负载,SMP负载均衡算法中,使用该值来比较CPU的负载大小
util_avgunsigned long实际算力。通常体现于一个调度实体或者CPU的实际算力需求
util_eststruct util_est用于估算FAIR进程的CPU使用率,和指数加权移动平均算法有关系,参考3.4节的表格

sched_entity包含sched_avg,描述调度实体的负载信息

cfs_rq也包含sched_avg,描述CFS队列的负载信息

sched_entity
sched_avg
cfs_rq
sched_avg

但是rt 、dl等不是这样的,需要另外再分析再看。

(1)可运行状态(runnable) 和正在运行状态(running)如何区分?

A进入就绪队列
se->on_rq = 1, runable状态
A出就绪队列
se->on_rq = 0

se在cfs_rq中,runnable状态,然后还要分成两个时间部分:

  • 正在运行时间,running时间
  • 在就绪队列中的时间,runnable时间,包含正在运行时间和等待运行时间

三、PELT算法

runnable_avg_yN_inv[ ]是内核为了方便计算,换算过得中间值。为了移位运算而不是浮点数运算。

static const u32 runnable_avg_yN_inv[] __maybe_unused = {
    0xffffffff, 0xfa83b2da, 0xf5257d14, 0xefe4b99a, 0xeac0c6e6, 0xe5b906e6,
    0xe0ccdeeb, 0xdbfbb796, 0xd744fcc9, 0xd2a81d91, 0xce248c14, 0xc9b9bd85,
    0xc5672a10, 0xc12c4cc9, 0xbd08a39e, 0xb8fbaf46, 0xb504f333, 0xb123f581,
    0xad583ee9, 0xa9a15ab4, 0xa5fed6a9, 0xa2704302, 0x9ef5325f, 0x9b8d39b9,
    0x9837f050, 0x94f4efa8, 0x91c3d373, 0x8ea4398a, 0x8b95c1e3, 0x88980e80,
    0x85aac367, 0x82cd8698,
};

#define LOAD_AVG_PERIOD 32     //1ms(1024us)为一个周期,32ms(32个周期)负载就会下降一半
#define LOAD_AVG_MAX 47742   //47742是无限个周期内(n无限大)的最大衰减负载总和值

比如当前负载是100,计算过去第32ms周期的负载值:((100 * runable_avg_yN_inv[31]) >> 32) = 51

LOAD_AVG_MAX

该宏是无限个周期内的最大衰减负载,为何是1024来乘呢?因为1ms这里计算为1024us(为了方便移位操作)

最大负载load_avg_max:

1024(1+y+y2 + y3 + ... ) = 1024*((1-yn)/(1-y)) = 47742 #这个公式可推导出来, yn趋向于0,这个无穷级数在-1<y<1时是收敛的

y=0.97855138(大约)
y^32 = 0.5(大约)

请添加图片描述

请添加图片描述

2021.04.25回过头来看这个无穷级数的使用,真是666啊,负载总体趋向一个确定值,因为它是收敛的,不是发散的。

反推PELT的衰减系数因子,保留3位小数,((1000 * runnable_avg_yN_inv[]) >> 32)/1000

//将runnable_avg_yN_inv[ ]折算成系数,保留3位小数:
static const u32 runnable_avg_yN_orig[] = {
    0.999, 0.978(这个值就是y), 0.957, 0.937, 0.917, 0.897,
    0.878, 0.859, 0.840, 0.822, 0.805, 0.787,
    ...
    0.522, 0.510,
};
//看上面这个数组,是不是就是 "y0, y, y2, y3, y4, ..., y31",对不对

3.1 dedcay_load–计算第n个周期的衰减负载值

**decay_load()**函数用于计算第n个周期的衰减负载值

/*
 * Approximate:
 *   val * y^n,    where y^32 ~= 0.5 (~1 scheduling period)
 */
/*val是第n个周期前的负载值,n是第n个周期*/
static u64 decay_load(u64 val, u64 n)
{
    unsigned int local_n;

    if (unlikely(n > LOAD_AVG_PERIOD * 63))    //如果n较大,大于32ms*63 = 2016ms,则太久远,负载可以不计算,结果为0
        return 0;

    /* after bounds checking we can collapse to 32-bit */
    local_n = n;

    /*
     * As y^PERIOD = 1/2, we can combine
     *    y^n = 1/2^(n/PERIOD) * y^(n%PERIOD)
     * With a look-up table which covers y^n (n<PERIOD)
     *
     * To achieve constant time decay_load.
     */
    //如果n是在32~2016之间,则n/32个周期直接就是右移多少位(因为每32ms,衰减一半相当于除以2),n%32再去查表找到对应的衰减系数
    if (unlikely(local_n >= LOAD_AVG_PERIOD)) {
        val >>= local_n / LOAD_AVG_PERIOD;   //右移n/32
        local_n %= LOAD_AVG_PERIOD;            //n%32就是查表的下标
    }

    val = mul_u64_u32_shr(val, runnable_avg_yN_inv[local_n], 32); //查表找到衰减系数,再乘以val,再右移32位,得到第n个周期的衰减负载值
    return val;
}

mul_u64_u32_shr函数简单看,就是用负载值(val * [runnable_avg_yN_inv系数]) >> 32

static inline u64 mul_u64_u64_shr(u64 a, u64 mul, unsigned int shift)
{
    return (u64)(((unsigned __int128)a * mul) >> shift);
}

cfs的load计算函数大体流程

请添加图片描述

3.2 __update_load_sum计算工作负载之和

函数__update_load_sum()

请添加图片描述

dl和rt调度器也会调用到这里,暂时先不看。

本文主要关心cfs调度器如何去更新其工作负载总和的。

代码注释中解释了:

用几何级数系列来计算历史负载贡献,以1ms为周期(1024us),p_0代表当前周期,p_N代表过去的第Nms周期

用u_i表示p_i周期中调度实体是可运行状态的时间部分,然后用u_i作为该周期计算负载的系数,用公司u_0 + u_1y + u_2y^2 + … 来表示历史负载,其中y^32=0.5(y约等于0.979),这就意味着:过去的第32ms周期,对当前最新周期u_0周期的负载计算贡献只贡献其当时的一半值

这个计算方式奇妙的地方我认为就在于:当一个周期过去后,我们就有了新的周期u_0’,直接用这个u_0’ + (之前load总和) * y 得到的就是新的负载总和

/*
 * [<- 1024us ->|<- 1024us ->|<- 1024us ->| ...
 *      p0            p1           p2
 *     (now)       (~1ms ago)  (~2ms ago)
 *
 * We then designate the fractions u_i as our co-efficients, yielding the
 * following representation of historical load:
 *   u_0 + u_1*y + u_2*y^2 + u_3*y^3 + ...
 *
 * When a period "rolls over" and we have new u_0`, multiplying the previous
 * sum again by y is sufficient to update:
 *   load_avg = u_0` + y*(u_0 + u_1*y + u_2*y^2 + ... )
 *            = u_0 + u_1*y + u_2*y^2 + ... [re-labeling u_i --> u_{i+1}]
 */
static __always_inline int
___update_load_sum(u64 now, struct sched_avg *sa,
          unsigned long load, unsigned long runnable, int running)
{
    u64 delta;

    delta = now - sa->last_update_time; //delta用于计算当前时间点与上次更新点的时间差,单位应该是ns
    /*
     * This should only happen when time goes backwards, which it
     * unfortunately does during sched clock init when we swap over to TSC.
     * 这里处理TSC特殊情况,可以不管
     */
    if ((s64)delta < 0) {
        sa->last_update_time = now;
        return 0;
    }

    /*
     * Use 1024ns as the unit of measurement since it's a reasonable
     * approximation of 1us and fast to compute.
     */
    delta >>= 10;  //将ns转为us,右移10位,也就是除以1024
    if (!delta)   //如果delta不足1us,则工作负载总和为0?
        return 0;

    sa->last_update_time += delta << 10;  //这就是下一次计算时的last_update_time,这次更新时间点;
    //sa->last_update_time = now? 这个地方为何不是直接等于now?也就是说这里记录的last_update_time是以us为单位的,少于1us的就没有记上,记在了后面的period_contrib里面

    /*
     * running is a subset of runnable (weight) so running can't be set if
     * runnable is clear. But there are some corner cases where the current
     * se has been already dequeued but cfs_rq->curr still points to it.
     * This means that weight will be 0 but not running for a sched_entity
     * but also for a cfs_rq if the latter becomes idle. As an example,
     * this happens during idle_balance() which calls
     * update_blocked_averages().
     *
     * Also see the comment in accumulate_sum().
     */
    //这里的load,如果是sched_entity,load则是!!se->on_rq(两个!!是把非0值都转为1,0值还是0),也就是有可能se已经不在runqueue里了,但cfs_rq->curr还指向该se,此时load应该是0; 如果是cfs_rq调用进来,则load是cfs_rq的权重值
    if (!load)
        runnable = running = 0;

    /*
     * Now we know we crossed measurement unit boundaries. The *_avg
     * accrues by two steps:
     *
     * Step 1: accumulate *_sum since last_update_time. If we haven't
     * crossed period boundaries, finish.
     */
    if (!accumulate_sum(delta, sa, load, runnable, running))
        return 0;

    return 1;
}
tatic u32 __accumulate_pelt_segments(u64 periods, u32 d1, u32 d3)
{
    u32 c1, c2, c3 = d3; /* y^0 == 1 */

    /*
     * c1 = d1 y^p
     */
    c1 = decay_load((u64)d1, periods);

    /*
     *            p-1
     * c2 = 1024 \Sum y^n
     *            n=1
     *
     *              inf        inf
     *    = 1024 ( \Sum y^n - \Sum y^n - y^0 )
     *              n=0        n=p
     */
    c2 = LOAD_AVG_MAX - decay_load(LOAD_AVG_MAX, periods) - 1024;

    return c1 + c2 + c3;                //d3时间段就不用乘以y系数,所以直接加上
}

/*
 * Accumulate the three separate parts of the sum; d1 the remainder
 * of the last (incomplete) period, d2 the span of full periods and d3
 * the remainder of the (incomplete) current period.
 *
 *           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)       //u*y^p就是p个周期之前的衰减总负载,就是下文step1中用decay_load()计算出来的load_sum
 *
 *                     p-1
 *      d1 y^p + 1024 \Sum y^n + d3 y^0        (Step 2)
 *                     n=1
 */
static __always_inline u32
accumulate_sum(u64 delta, struct sched_avg *sa,
           unsigned long load, unsigned long runnable, int running)
{
    u32 contrib = (u32)delta; /* p == 0 -> delta < 1024 */
    u64 periods;

    delta += sa->period_contrib;  //这里就是两次更新之间的时间差 + 上一次更新的时间差%1024(就是上次更新时没有满足一个1024us周期的)
    periods = delta / 1024; /* A period is 1024us (~1ms) */

    /*
     * Step 1: decay old *_sum if we crossed period boundaries.
     */
    if (periods) {   //如果横跨了一个周期或以上,就需要进行负载贡献衰减计算了
        sa->load_sum = decay_load(sa->load_sum, periods); //这里就只计算了前面第periods周期的工作负载,刚好此时的load_sum是periods周期之前的load_sum,这样理解就符合decay_load的函数作用了---计算第n周期的工作负载
        sa->runnable_sum =
            decay_load(sa->runnable_sum, periods);
        sa->util_sum = decay_load((u64)(sa->util_sum), periods);

        /*
         * Step 2
         */
        delta %= 1024;
        if (load) {
            /*
             * This relies on the:
             *
             * if (!load)
             *    runnable = running = 0;
             *
             * clause from ___update_load_sum(); this results in
             * the below usage of @contrib to dissapear entirely,
             * so no point in calculating it.
             */
            contrib = __accumulate_pelt_segments(periods,
                    1024 - sa->period_contrib, delta);
        }
    }
    sa->period_contrib = delta; //这里是保存的这次计算完后,取余1024,也就是不满足一个1024us周期的时间

    //这里load,如果是sched_entity,它在on_rq,那么load为1,也就是它算下来这d1+d2+d3的时间里的贡献就是contrib值
    //如果是cfs_rq,则load是权重,也就是用weight再乘以contrib系数,这里代码复用的很巧妙啊
    if (load)
        sa->load_sum += load * contrib;
    if (runnable)
        sa->runnable_sum += runnable * contrib << SCHED_CAPACITY_SHIFT;
    if (running)
        sa->util_sum += contrib << SCHED_CAPACITY_SHIFT;

    return periods;
}

整理了一下整体过程,配合代码看,一下子就明白了

请添加图片描述

上面一个是我一边看代码一边画的,看完代码自己再根据理解重新画一下逻辑,就更简单了:
请添加图片描述

3.3 更新se或cfs_rq的负载

两个函数__update_load_avg_se(), __update_load_avg_cfs_rq()

问题1:__update_load_avg总是在判断__update_Load_sum是否为1之后才调用,这是为什么?

解答:在3.4节中解答

问题2:对于调度实体和调度队列,__update_load_sum的入参load、runnable、running分别意味着什么

解答:

对于sched_entity,load是0或1(看on_rq),runnable也是0或1(也是看on_rq), running还是0或1布尔值,看是否是当前正在运行的

对于cfs_rq,load是队列权重,runnable是CFS就绪队列里中包含组调度里所有可运行状态的进程数量,running是cfs_rq当前curr是否为空判断0或1

int __update_load_avg_se(u64 now, struct cfs_rq *cfs_rq, struct sched_entity *se)
{
    /*对于sched_entity,load是0或1(看on_rq),runnable也是0或1(也是看on_rq), running还是0或1布尔值,看是否是当前正在运行的*/
    if (___update_load_sum(now, &se->avg, !!se->on_rq, se_runnable(se),
                cfs_rq->curr == se)) {

        ___update_load_avg(&se->avg, se_weight(se));
        cfs_se_util_change(&se->avg);
        trace_pelt_se_tp(se);
        return 1;
    }

    return 0;
}

int __update_load_avg_cfs_rq(u64 now, struct cfs_rq *cfs_rq)
{
    /*对于cfs_rq,load是队列权重,runnable是CFS就绪队列里中包含组调度里所有可运行状态的进程数量,running是cfs_rq当前curr是否为空判断0或1*/
    if (___update_load_sum(now, &cfs_rq->avg,
                scale_load_down(cfs_rq->load.weight),
                cfs_rq->h_nr_running,
                cfs_rq->curr != NULL)) {

        ___update_load_avg(&cfs_rq->avg, 1);
        trace_pelt_cfs_tp(cfs_rq);
        return 1;
    }

    return 0;
}

再看一下accumulate_sum函数的最后几句:

    sa->period_contrib = delta; //这个是记录下up2时间点之前的最近的一个周期点的时间间隙,就是上面手绘图里的d3

    if (load)
        sa->load_sum += load * contrib;  //这里要区分se还是cfs_rq,se如果是在队列里load=1,cfs_rq的load则是权重;
    if (runnable)
        sa->runnable_sum += runnable * contrib << SCHED_CAPACITY_SHIFT; 
    if (running)
        sa->util_sum += contrib << SCHED_CAPACITY_SHIFT;

    return periods;
sched_avg部分成员调度实体sched_entity调度队列cfs_rq
load_sum调度实体在可运行状态的累计衰减总时间cfs_rq中所有在可运行状态下进程的累计工作负载总和
runnable_sum调度实体在可运行状态下的累计衰减总时间,等于load_sum《奔跑吧linux内核》:cfs_rq在可运行状态下的工作负载,一般等于load_sum
《我的理解》:cfs_rq中包含组调度中所有可运行状态下进程的累计衰减总时间之和,单位us?
util_sum《奔跑吧linux内核》:调度实体在可运行状态下的总算力;
《我的理解》:就是调度实体的占用cpu的累计衰减总时间,cpu使用率?
《奔跑吧linux内核》:cfs_rq在可运行状态下的总算力;
《我的理解》:就是cfs_rq的占用cpu的累计衰减总时间,cpu使用率?

《内核工匠》博客里的解释如下,我觉得这个解释才是到位的:

请添加图片描述

3.4 __update_load_avg计算量化负载

函数__update_load_avg()

该函数为何这么设计-没看懂
/*
 * When syncing *_avg with *_sum, we must take into account the current
 * position in the PELT segment otherwise the remaining part of the segment
 * will be considered as idle time whereas it's not yet elapsed and this will
 * generate unwanted oscillation in the range [1002..1024[.
 *
 * The max value of *_sum varies with the position in the time segment and is
 * equals to :
 *
 *   LOAD_AVG_MAX*y + sa->period_contrib
 *
 * which can be simplified into:
 *
 *   LOAD_AVG_MAX - 1024 + sa->period_contrib
 *
 * because LOAD_AVG_MAX*y == LOAD_AVG_MAX-1024
 *
 * The same care must be taken when a sched entity is added, updated or
 * removed from a cfs_rq and we need to update sched_avg. Scheduler entities
 * and the cfs rq, to which they are attached, have the same position in the
 * time segment because they use the same clock. This means that we can use
 * the period_contrib of cfs_rq when updating the sched_avg of a sched_entity
 * if it's more convenient.
 */
static __always_inline void
___update_load_avg(struct sched_avg *sa, unsigned long load)
{
    u32 divider = get_pelt_divider(sa);

    /*
     * Step 2: update *_avg.
     */
    sa->load_avg = div_u64(load * sa->load_sum, divider); // load_sum / (1024y+period_contrib)
    sa->runnable_avg = div_u64(sa->runnable_sum, divider); // runnable_sum / (1024y+period_contrib)
    WRITE_ONCE(sa->util_avg, sa->util_sum / divider);   //util_sum /(1024y+period_contrib)
}

static inline u32 get_pelt_divider(struct sched_avg *avg)
{
    return LOAD_AVG_MAX - 1024 + avg->period_contrib;
}

请添加图片描述

__update_load_avg看注释我也没看懂为何要这么计算

1024 /(1-y)  = LOAD_AVG_MAX
LOAD_AVG_MAX * y = LOAD_AVG_MAX - 1024

但是为何需要用load_sum/(LOAD_AVG_MAX -1024 + period_contrib这个零散时间)来算平均负载贡献时间呢

为何是要处理 ( 1024y + period_contrib )这个时间呢?
看注释是说,这个时间段的__sum值是最大的,不能不考虑period_contrib这段时间,如果不考虑进来,则认为period_contrib这个后面还不满一个周期的时间是idle的,会导致在[1002,1024]之间震荡?
没理解这地方的原因,why?
是不是可以理解,load_avg是计算的平均负载嘛,就要用将load_sum按照最长累计衰减时间(一个周期左右)来进行等分?所以记上period_contrib

__update_load_avg总是在判断__update_Load_sum是否为1之后才调用,这是为什么?

解答:未能找到说服我自己的答案; period_contrib(也就是时间轴图里的d3)如果为0,则不需要执行update load_avg了?也就说,up2与up1之前的那个周期节点之间刚好是若干个周期时间,此时为何load_sum增加了,但load_avg就不更新了?不更新load_avg是不是就意味着load_avg没有变?没有变意味着啥,load_avg也不能以有无period_contrib来决定要不要更新啊?period_contrib是跟update_time有关系的

实在没理解__update_load_avg这个函数为何需要这么做?

3.5 PELT中的负载计算接口API

cfs调度器

通过update_load_avg接口进入pelt中的算法。更新调度实体和调度队列的负载

请添加图片描述

下图中是先调用 __update_load_sum, 再调用 __update_load_avg
请添加图片描述

rt调度器

请添加图片描述

/*
 * rt_rq:
 *
 *   util_sum = \Sum se->avg.util_sum but se->avg.util_sum is not tracked
 *   util_sum = cpu_scale * load_sum
 *   runnable_sum = util_sum
 *
 *   load_avg and runnable_avg are not supported and meaningless.
 *
 */

int update_rt_rq_load_avg(u64 now, struct rq *rq, int running)
{
    if (___update_load_sum(now, &rq->avg_rt,
                running,
                running,
                running)) {

        ___update_load_avg(&rq->avg_rt, 1);
        trace_pelt_rt_tp(rq);
        return 1;
    }

    return 0;
}
dl调度器
/*
 * dl_rq:
 *
 *   util_sum = \Sum se->avg.util_sum but se->avg.util_sum is not tracked
 *   util_sum = cpu_scale * load_sum
 *   runnable_sum = util_sum
 *
 *   load_avg and runnable_avg are not supported and meaningless.
 *
 */

int update_dl_rq_load_avg(u64 now, struct rq *rq, int running)
{
    if (___update_load_sum(now, &rq->avg_dl,
                running,
                running,
                running)) {

        ___update_load_avg(&rq->avg_dl, 1);
        trace_pelt_dl_tp(rq);
        return 1;
    }

    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/43548.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Flutter 创建自己的对话框,不使用任何包!

创建自己的对话框&#xff0c;不使用任何包&#xff01; 原文 https://itnext.io/create-your-own-dialog-without-using-any-packages-7bb303f62471 前言 在本文中&#xff0c;我们将学习如何创建我们自己的 showDialog() 函数&#xff0c;并了解到底发生了什么。 正文 先看效…

Matplotlib 可视化50图:散点图(1)

导读 本系列将持续更新50个matplotlib可视化示例&#xff0c;主要参考Selva Prabhakaran 在MachineLearning Plus上发布的博文&#xff1a;Python可视化50图。 定义 关联图是查看两个事物之间关系的图像&#xff0c;它能够展示出一个事物随着另一个事物是如何变化的。关联图的类…

ctfshow(菜狗杯)

目录 web签到 一言既出 驷马难追 web2 c0me_t0_s1gn 我的眼里只有$ TAPTAPTAP Webshell 化零为整 无一幸免 遍地飘零 传说之下&#xff08;雾&#xff09; Is_Not_Obfuscate web签到 <?phperror_reporting(0); highlight_file(__FILE__);eval($_REQUEST[$_GET[…

springboot大学生课堂考勤管理系统的设计与实现

根据一般学生课堂考勤管理系统的功能需求分析&#xff0c;本系统的功能模块如下&#xff1a; &#xff08;1&#xff09;在个人中心&#xff0c;管理员可以修改自己的用户名和登录密码。 &#xff08;2&#xff09;在学生管理模块中&#xff0c;可以查看学生的信息&#xff0c;…

DiffusionDet:Diffusion Model for Object Detection

Diffusion Model for Object Detection 一种用于目标检测的扩散模型 Motivation 1、如何使用一种更简单的方法代替可查询的object queries 2、Bounding box的生成方式过去是三种&#xff0c;第一种为sliding windows、第二种anchor box、第三种object queries&#xff0c;这里其…

AlphaFold2源码解析(3)--数据预处理

AlphaFold2源码解析(3)–数据预处理 数据预处理整体流程 数据处理入口&#xff1a; feature_dict data_pipeline.process( input_fasta_pathfasta_path,# 输入序列目录 msa_output_dirmsa_output_dir) # MSA序列目录 可能是单体也可能是多聚体 主要调动的API是&#xff1a; …

如何让Java项目兼容更多的客户端设备(二)

如何让Java项目兼容更多的客户端设备&#xff08;二&#xff09; ​ ​ 一、Token认证的原理 传统的单体JavaWeb项目通常采用HttpSession保存登陆成功的凭证&#xff0c;但是HttpSession需要浏览器的Cookie机制配合。也就是说Web项目的客户端只能是浏览器&#xff0c;不可以…

torch.nn.functional.grid_sample(F.grid_sample)函数的说明 3D空间中的点向图像投影的易错点

由于二者有一定共通之处&#xff0c;因此放在一篇文章内介绍。 1. 关于torch.nn.functional.grid_sample函数的说明&#xff08;F.grid_sample&#xff09; 该函数的作用是在图像/体素空间中采样特征。 1.1 输入和输出&#xff1a; 变量名数据类型默认值含义备注inputTensor-…

训练神经网络的各种优化算法【文末赠书】

正确的优化算法可以成倍地减少训练时间 许多人在训练神经网络时可能会使用优化器&#xff0c;而不知道该方法称为优化。优化器是用于更改神经网络属性&#xff08;例如权重和学习率&#xff09;以减少损失的算法或方法。 文章目录梯度下降随机梯度下降小批量梯度下降其它优化算…

C/C++指针之提高篇详解(二)

一、引言 C/C语言中引入了指针&#xff0c;使得程序能够直接访问内存地址&#xff0c;使得很多复杂的操作变得简单&#xff0c;同时也提高了程序的运行效率。指针即是地址&#xff0c;但是地址却是通过指针变量来存储的。这就好比我们的教室&#xff0c;每个教室都有一个房间号…

第3章 Tomcatservlet

1.BS与CS *CS&#xff1a;客户端服务器架构模式 优点&#xff1a;充分利用客户端机器的资源&#xff0c;减轻服务器的负荷 缺点&#xff1a;需要安装&#xff1b;升级维护成本较高 *BS&#xff1a;浏览器服务器架构模式 优点&#xff1a;客户端不需要安装&#xff0c;维护成本…

Zlibrary已死,找了一个替代品,找了一个替代品免费的电子书下载平台...

大家好&#xff0c;我是鸟哥。一个半路出家的程序员。 提到Zlibrary&#xff0c;想必大家都不陌生吧。全球最大的数字图书馆&#xff0c;截止被封前共收录了591万本书&#xff0c;7751万篇文章&#xff0c;并且还在不断的增加中&#xff0c;关键是可以免费下载。 反正我是很熟悉…

Rust之常用集合(二):字符串(String)

开发环境 Windows 10Rust 1.65.0VS Code 1.73.1 项目工程 这里继续沿用上次工程rust-demo 用字符串存储UTF-8编码的文本 我们在之前的章节中讨论了字符串&#xff0c;但现在我们将更深入地研究它们。新的Rust人会因为三个原因而陷入字符串:Rust倾向于暴露可能的错误&#x…

【季报分析】美团:真的很美

11月25日&#xff0c;港股盘后美团发布了2022年第三季度业绩报告。整体来看&#xff0c;财报数据还算不错&#xff0c;毕竟大部分指标都已经超预期&#xff0c;探员利用这两天假期&#xff0c;把美团的这份财报仔细过了一遍&#xff0c;当然探员也把2022年Q1、Q2的数据也进行了…

Linux代码调试----gdb使用介绍

目录 一、简介 二、gdb使用流程&#xff1a; &#xff08;1&#xff09;gdb的启动 &#xff08;2&#xff09;调试中查看源代码 &#xff08;3&#xff09;开始调试 一、简介 GDB 全称“GNU symbolic debugger”&#xff0c;是 Linux 下常用的程序调试器。发展至今&#xff…

[附源码]SSM计算机毕业设计校园超市进销存管理系统JAVA

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

【Node.js】第九章 数据库

目录 1. 数据库的基本概念 1.1 数据库的概念 1.2 数据库的分类 1.3 数据组织结构 2. MySQL使用介绍 2.1 安装MySQL和MySQL Workbench 2.2 MySQL的基本使用 2.3 使用SQL管理数据库 3. 在项目中使用MySQL 3.1 安装和配置MySQL模块 3.2 操作数据 1. 数据库的基本概念…

word怎么转pdf?word转pdf借助pdf软件即可搞定!

我们在办公的时候&#xff0c;会有word转pdf的办公需求&#xff0c;但是很多人都不清楚word转pdf的方法&#xff0c;那么word怎么转pdf呢&#xff1f;其实方法很简单&#xff0c;利用pdf软件来进行word转pdf的操作即可&#xff0c;如今&#xff0c;市面上pdf软件应接不暇&#…

五、Javascript 空间坐标[尺寸、滑动]

一、尺寸 1.视窗尺寸 document.documentElement.clientWidth&#xff1a;视窗宽度document.documentElement.clientHeight&#xff1a;视窗高度 2.各种尺寸 举例&#xff1a;<div id"gao"></div> 前提&#xff1a;var a document.getElementById(g…

多重共线性如何分析?

判断标准 常见的直观判断方法共有四个&#xff0c;如下&#xff1a; &#xff08;1&#xff09;某些自变量的相关系数值较大&#xff08;比如大于0.8&#xff09;等&#xff0c;可以利用pearson相关系数检验法一般是利用解释变量之间的线性相关程度判断&#xff0c;一般标准是…