Chinaunix首页 | 论坛 | 博客
  • 博客访问: 752142
  • 博文数量: 79
  • 博客积分: 2671
  • 博客等级: 少校
  • 技术积分: 1247
  • 用 户 组: 普通用户
  • 注册时间: 2010-04-02 15:26
个人简介

宅男

文章分类

全部博文(79)

文章存档

2017年(11)

2016年(12)

2015年(6)

2012年(10)

2011年(33)

2010年(7)

分类: Android平台

2017-07-07 16:57:14

这一篇准备介绍一下CFSPELTper entity load tracking)。这一套用来量化进程的负载的模型。这套模型在__update_load_avg函数上面的注释中有详细的解释。

其主要思想是将进程的运行时间切分为以1ms为单位的时间片段。过去的每一个时间片段对于进程负载的影响也不同。为了便于计算,将1ms设置为1024us

* [<- 1024us="" -="">|<- 1024us="" -="">|<- 1024us="" -="">| ...

 *      p0            p1           p2

 *     (now)       (~1ms ago)  (~2ms ago)

假设了一个时间因子y,那么进程的负载可以表示为:

u_0 + u_1*y + u_2*y^2 + u_3*y^3 + ...

那么这个时间因子是多少呢?linux内核将其设置为 y^32 = 0.5 .即没隔32ms,其影响力为原来的一半。

那么最大的负载(load)是多少呢?1024(1+y+y2 + y3 + ... ) = 1024*(y/1-y) = 47742

所以内核定义了几个宏

#define LOAD_AVG_PERIOD 32

#define LOAD_AVG_MAX 47742 /* maximum possible load avg */

#define LOAD_AVG_MAX_N 345 /* number of full periods to produce LOAD_AVG_MAX */

其中LOAD_AVG_PERIOD32,即每隔32ms,衰减一半。LOAD_AVG_MAX为最大的load值。LOAD_AVG_MAX_N为达到LAOD_AVG_MAX,进程所需要连续运行的时间。

下面直接进入函数。

static u32 __compute_runnable_contrib(u64 n)

{

        u32 contrib = 0;

 

        if (likely(n <= LOAD_AVG_PERIOD))

                return runnable_avg_yN_sum[n];

        else if (unlikely(n >= LOAD_AVG_MAX_N))

                return LOAD_AVG_MAX;

为了方便计算,linux预设值了一个算好的数组。

/*

 * Precomputed \Sum y^k { 1<=k<=n }.  These are floor(true_value) to prevent

 * over-estimates when re-combining.

 */

static const u32 runnable_avg_yN_sum[] = {

            0, 1002, 1982, 2941, 3880, 4798, 5697, 6576, 7437, 8279, 9103,

         9909,10698,11470,12226,12966,13690,14398,15091,15769,16433,17082,

        17718,18340,18949,19545,20128,20698,21256,21802,22336,22859,23371,

};

其中计算的值是在不同N值下,1024y+y2 + y3 + ... ) 计算出来的值。

由于每隔32ms需要衰减一半,所以只需要计算32个值就可以了。

再次回到代码。

        if (likely(n <= LOAD_AVG_PERIOD))

                return runnable_avg_yN_sum[n];

        else if (unlikely(n >= LOAD_AVG_MAX_N))

                return LOAD_AVG_MAX;

如果n值小于等于32,直接从预先算好的数组获取就可以了,如果N值大于LOAD_AVG_MAX_N,就获得了最大loadLOAD_AVG_MAX

        /* Compute \Sum k^n combining precomputed values for k^i, \Sum k^j */

        do {

                contrib /= 2; /* y^LOAD_AVG_PERIOD = 1/2 */

                contrib += runnable_avg_yN_sum[LOAD_AVG_PERIOD];

 

                n -= LOAD_AVG_PERIOD;

        } while (n > LOAD_AVG_PERIOD);

 

        contrib = decay_load(contrib, n);

        return contrib + runnable_avg_yN_sum[n];

do while循环中,每隔32ms衰减一半,并加上最近的这32ms中的load值。

contrib = decay_load(contrib, n)用于计算contrib在经过nms之后剩余多少。

decay_load这个函数的注释就可以看出来这段函数的作用。

/*

 * Approximate:

 *   val * y^n,    where y^32 ~= 0.5 (~1 scheduling period)

 */

static __always_inline u64 decay_load(u64 val, u64 n)

 

再次回到__update_load_avg用于计算一个进程的对于load的贡献。

其中weight是指进程的weight值。

static __always_inline int

__update_load_avg(u64 now, int cpu, struct sched_avg *sa,

                  unsigned long weight, int running, struct cfs_rq *cfs_rq)

{

        u64 delta, scaled_delta, periods;

        u32 contrib;

        unsigned int delta_w, scaled_delta_w, decayed = 0;

        unsigned long scale_freq, scale_cpu;

 

        delta = now - sa->last_update_time;

//last_update_time为上一次更新load的时间。

        /*

         * This should only happen when time goes backwards, which it

         * unfortunately does during sched clock init when we swap over to 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;

        if (!delta)

                return 0;

由于PELT中是为ms以及us为单位计算load值的,而内核的时间是以ns为单位的。

所以需要将ns转变成us,为了计算简单,以1us=1024ns来计算。

        scale_freq = arch_scale_freq_capacity(NULL, cpu);

        scale_cpu = arch_scale_cpu_capacity(NULL, cpu);

 

        /* delta_w is the amount already accumulated against our next period */

        delta_w = sa->period_contrib;

由于超过1024也就是1ms。就需要衰减。所以这边需要判断delta+sa->period_contrib.

        if (delta + delta_w >= 1024) {

                decayed = 1;

 

                /* how much left for next period will start over, we don't know yet */

                sa->period_contrib = 0;

       

                /*

                 * Now that we know we're crossing a period boundary, figure

                 * out how much from delta we need to complete the current

                 * period and accrue it.

                 */

                delta_w = 1024 - delta_w;

                scaled_delta_w = cap_scale(delta_w, scale_freq);

                if (weight) {

                        sa->load_sum += weight * scaled_delta_w;

                        if (cfs_rq) {

                                cfs_rq->runnable_load_sum +=

                                                weight * scaled_delta_w;

                        }

                }

                if (running)

                        sa->util_sum += scaled_delta_w * scale_cpu;

 

                delta -= delta_w;

                /* Figure out how many additional periods this update spans */

                periods = delta / 1024;

                delta %= 1024;

 

                sa->load_sum = decay_load(sa->load_sum, periods + 1);

                if (cfs_rq) {

                        cfs_rq->runnable_load_sum =

                                decay_load(cfs_rq->runnable_load_sum, periods + 1);

                }

                sa->util_sum = decay_load((u64)(sa->util_sum), periods + 1);

 

                /* Efficiently calculate \sum (1..n_period) 1024*y^i */

                contrib = __compute_runnable_contrib(periods);

                contrib = cap_scale(contrib, scale_freq);

                if (weight) {

                        sa->load_sum += weight * contrib;

                        if (cfs_rq)

                                cfs_rq->runnable_load_sum += weight * contrib;

                }

                if (running)

                        sa->util_sum += contrib * scale_cpu;

        }

这里面涉及到sa的两个成员变量:util_sumload_sum

其中util_sumtask出于running状态的load,其中util是指utilization,后面根据CPU使用率的情况调整频率的时候,需要用到该变量。而load_sum是指出于runnable(包括running)时间的load

其中arch_scale_freq_capacity

#define  

unsigned long cpufreq_scale_freq_capacity(struct sched_domain *sd, int cpu)

{

        return per_cpu(freq_scale, cpu);

}

下面是计算freq_scale的代码。

        unsigned long scale = (cur << SCHED_CAPACITY_SHIFT) / policy->max;

//根据max freq来计算当前频率对应的capacity,最大值为1024.

        struct cpufreq_cpuinfo *cpuinfo = &policy->cpuinfo;

        int cpu;

 

        pr_debug("cpus %*pbl cur/cur max freq %lu/%u kHz freq scale %lu\n",

                 cpumask_pr_args(policy->cpus), cur, policy->max, scale);

 

        for_each_cpu(cpu, policy->cpus)

                per_cpu(freq_scale, cpu) = scale;

 

另外arch_scale_cpu_capacity

#define  

由于B-L架构存在CPU之间的性能差异,所以除了计算当前freq带来的归一化的系数。CPU之间的差异同样影响load的计算。就是用来计算CPU的影响因子。

所以在调用__update_load_avg计算taskload的时候,需要同时考虑当前frequency以及CPU带来的影响。

PELT几乎是fair的核心。它同时决定了scheduler的两大核心,即选择那个task,选择那个CPU

(1)       rq上调用pick_next_task选择下一个可执行的fair task时,需要选择vruntime最小的进程或者group,如果是group的话,继续递归下去从group中选择vruntime最小的。直到选择到一个task为止。

(2)       new task或者wake uptask选择rq的时候,需要选择一个idlestCPU,即整体负载load最小的rq

如果仔细查看select_task_rq_fair函数就会发现这套代码基本是为SMP架构设计的。现在只是简单的打了一些补丁而已,其实并不适合于现在手机上普遍使用的BL架构的SOC。因为这里面基本只考虑的load的影响,没有考虑BL中大小核的功耗的差异,因为大小核之间在功耗以及IPC相关差异比较明显。到底怎么量化需要另外一套模型。

同时PELT也是不太适合于手机等设备的,PELT根据运行时间来计算历史累计的负载。存在两方面的问题

(1)       PELT在计算一个task的累积影响时存在很大的延迟,注意上面提到的LOAD_AVG_MAX_N宏,即一个100%task从创建开始,需要持续出于runnable状态345ms才能达到最大load值。

(2)       同样由于只计算runnable的累计load,如果一旦task由于某种原因进入sleep状态,那么其load值就急剧衰减,大约213ms就衰减为0.

这两个影响使得PELT并不适用于对于时间比较敏感的设备,如手机,平板电脑等等。

当然CFS里面的代码太多,而且太过于繁杂,很多都是关于cgroup以及load balance相关的,在后面会一点点剖析。

阅读(4764) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~