Linux系统中负载(Load Average)的含义与计算方法


负载的查看

在Linux系统中,使用uptimewtop命令都可以查看负载,或者直接通过cat /proc/loadavg查看。

比如说uptime命令:

20:56:33 up 22 min, 3 users, load average: 0.39, 0.25, 0.10

可以看到有三个值,这三个值分别表示:

  • 前1分钟系统的平均负载
  • 前5分钟系统的平均负载
  • 前15分钟系统的平均负载

下面就来看看负载的含义。


负载的含义

负载的含义是运行队列的平均长度

具体而言,对于Linux系统,其统计了最近1分钟、最近5分钟、最近15分钟处于running或者uninterruptible sleep状态的进程数。

文末参考资料里给出了一个车道的例子进行类比,这儿就不赘述了。举几个例子:

  • 负载为0.5,表示CPU有平均一半的时间是空闲的
  • 负载为1,这时就表示CPU被充分利用了
  • 负载为2,则表示负载过大,有一半的任务在等待CPU执行

一般认为负载为0.7是警戒线。

负载与多处理器

对于多处理器,相当于多车道。比如对于双处理器,那么负载为2才算满。 可以简单地将负载除以CPU核心数换算成单处理的情况。

负载与CPU使用率

文末参考资料中提到:

The CPU percentage shows us how much the cars are using the freeway, but the load averages show us the whole picture, including pent-up demand.

相比于CPU使用率,负载可能展示更多的信息,即CPU的需求量。

负载和多线程

Wikipedia提到在不同的Unix系统中多线程负载的计算方式有很多种,比如线程与核实体间不同的模型就有所不同。而在Linux系统中每个线程应该都是独立计算的。


负载的计算

我并不了解统计学等知识,以下只是个人一些粗浅的理解。

负载计算原理是统计学的EWMAExponentially Damped/Weighted Moving Average),利用指数衰减函数递推出当前序列平均值。

Wikipedia里提到:

Hence, the 1-minute load average consists of 63% (more precisely: 1 - 1/e) of the load from the last minute and 37% (1/e) of the average load since start up, excluding the last minute. For the 5- and 15-minute load averages, the same 63%/37% ratio is computed over 5 minutes and 15 minutes respectively. Therefore, it's not technically accurate that the 1-minute load average only includes the last 60 seconds of activity (it actually includes 37% of the activity from the past), but it is correct to state that it includes mostly the last minute.

即统计计算出一段时间的负载,其本质是计算均值。比如说:

  • 要计算——最近x分钟负载:$la_{-x}$
  • 假设已知——自系统启动到x分钟前的负载为:$la_{+x}$
  • 假设可以得到——近x分钟的运行的任务数为:$n_x$

那么 $$la_{-x} = la_{+x} \times (e^{-1}) \ + \ n_x \times (1-e^{-1})$$ 即 $$la_{-x} = la_{+x} \times 0.37 \ + \ n_x \times 0.63$$

可以看出$x$分钟前的负载权重是比较小的,随着时间的推移前面的信息影响会越来越小,直到趋近于0,最近时间的信息影响的比重比较大。

而在Linux系统中,是每间隔$5HZ$或者$5秒$统计一次运行的任务数,然后计算均值。那么随着时间推移,可以得出这么一个序列:

$$n_1, n_2, n_3, ..., n_{t-1}, n_t$$

当前时刻$t$的Moving Average——$mv_t$同样根据$mv_{t-1}$计算出:

$$mv_t = mv_{t-1} \times (e^{-\lambda}) + n_t \times (1-e^{-\lambda})$$

对于要统计最近1分钟(60秒)的均值,由于时间间隔为5秒,那么$\lambda$的取值应为$\frac 5{60}$;同理,最近5分钟(300秒)的均值,$\lambda$的取值应为$\frac 5{300}$;最近15分钟(900秒)的均值,$\lambda$的取值应为$\frac 5{900}$。

下面来看看Linux中如何实现负载的计算。


负载计算的实现

Linux内核中维护了一个Jiffies变量,记录自开机一开经历的Tick数,每次更新Jiffies时系统会调用timer.c中的do_timer()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
unsigned long avenrun[3];
static inline void calc_load(unsigned long ticks)
{
unsigned long active_tasks; /* fixed-point */
static int count = LOAD_FREQ;
count -= ticks;
if (count < 0) {
count += LOAD_FREQ;
active_tasks = count_active_tasks();
CALC_LOAD(avenrun[0], EXP_1, active_tasks);
CALC_LOAD(avenrun[1], EXP_5, active_tasks);
CALC_LOAD(avenrun[2], EXP_15, active_tasks);
}
}

sched.h中可以看到上面calc_load()里一些变量、宏的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
extern unsigned long avenrun[]; /* Load averages */
#define FSHIFT 11 /* nr of bits of precision */
#define FIXED_1 (1<<FSHIFT) /* 1.0 as fixed-point */
#define LOAD_FREQ (5*HZ) /* 5 sec intervals */
#define EXP_1 1884 /* 1/exp(5sec/1min) as fixed-point */
#define EXP_5 2014 /* 1/exp(5sec/5min) */
#define EXP_15 2037 /* 1/exp(5sec/15min) */
#define CALC_LOAD(load,exp,n) \
load *= exp; \
load += n*(FIXED_1-exp); \
load >>= FSHIFT;

可以看到,每次Jiffies+1时调用calc_load(),并不会马上统计计算负载信息,而是等到一个LOAD_FREQ的周期结束,随后又开始新的LOAD_FREQ周期;

通过调用count_active_tasks()可以获得处于running或者uninterruptible sleep状态的进程数;

最后执行三次的CALC_LOAD,分别更新统计的1分钟、5分钟和15分钟的负载信息。

值得一提的就是CALC_LOAD的实现,其本质是将上面的Moving Average计算的式子乘以$2^{11}$,最后再除以$2^{11}$,采用位运算并且避免了浮点数运算,提高了计算效率。


参考资料

  1. https://yq.aliyun.com/articles/41856
  2. http://en.wikipedia.org/wiki/Load_%28computing%29
  3. http://www.linuxjournal.com/article/9001