Linux性能优化-CPU

一、平均负载

1.1、什么是平均负载

17:33:53 当前时间

up 57 min,系统运行时间

3 users 正在登录的用户

后面三个分别代表:最近1分钟、5分钟、15分钟平均负载

平均负载:单位时间内,系统处于可运行状态不可中断状态的平均进程数,也就是平均活跃进程数

可运行状态:正在使用CPU(Running)或正在等待CPU(Runnable)的进程。

不可中断状态:正处于内核态关键流程中的进程,并且这些流程是不可打断的。

平均活跃进程数:单位时间内活跃的进程数,是活跃进程数的指数衰减值。

1.2、平均负载的合理值

    查看CPU个数

    grep 'model name' /proc/cpuinfo |wc -l

    当平均负载大于CPU个数当时候就出现了过载。(平均负载不要高于CPU数量70%)

1.3、平均负载与CPU使用率

平均负载是指单位时间内,处于可运行状态和不可中断状态的进程数。包括正在使用CPU的进程,等待CPU等待I/O的进程。

CPU使用率是单位时间内CPU繁忙情况的统计 。跟平均负载不一定相对应。比如:

  • CPU密集型进程,使用大量CPU会导致平均负载升高,此时两者一致。
  • I/O密集型进程,等待I/O也会导致平均负载升高,但是CPU 使用率不一定高。
  • 大量等待CPU的进程调度也会导致平均负载升高,此时CPU使用率也会比较高。

二、CPU上下文切换

2.1、上下文切换

CPU上下文切换就是先把前一个任务的CPU上下文(也就是CPU寄存器和程序计数器)保存起来,然后加载新任务的上下文到寄存器和程序计数器,最后再跳转到程序计数器所指到新位置,运行新任务。

2.2、上下文切换分类

1、进程上下文切换

Linux按照特权等级,把进程的运行空间分为内核空间和用户空间。

  • 内核空间:具有最高权限,可以直接访问各种资源;
  • 用户空间:只能访问受限资源,不能直接访问内存等硬件设备,必须通过系统调用陷入到内核中,才能访问这些特权资源。

从用户态切换到内核态是通过系统调用来玩的。比如,当我们查看文件的过程:

1)调用open()打开文件;

2)调用read()读取文件内容;

3)调用write()将文件写到标准输出;

4)调用close()关闭文件;

CPU寄存器里原来用户态的指令位置,需要先保存起来。然后CPU寄存器更新为内核态指令的新位置,最后跳转到内核态运行内核任务。系统调用结束后,CPU寄存器恢复原来保存的用户态,然后再切换到用户空间,继续运行进程。所以,一次系统调用的过程,发生了两次CPU上下文切换

系统调用的过程不会涉及到虚拟内存等进程用户态的资源,也不会切换进程。

  • 进程上下文切换是指从一个进程切换到另一个进程;
  • 系统调用过程一直在同一个进程中进行;

系统调用过程称为特权切换,而不是上下文切换。

进程上下文切换跟系统调用的区别?

首先,进程由内核管理和调度的,进程的切换只发生在内核态。所以,进程的上下文切换不仅包含了虚拟内存、栈、全局变量等用户空间资源,还包含内核堆栈、寄存器等内核空间资源。

因此,进程上下文切换比系统调用多了一步,在保存当前进程的内核状态和CPU寄存器之前,需要先把该进程的虚拟内存、栈等先保存下来;而加载了下一进程的内核态后,还需要刷新进程的虚拟内存和用户栈。

如下图所示:保存上下文和恢复上下文并不是免费的,需要内核在CPU上运行才能完成。

什么时候会发生上下文切换?

进程切换的时候才会发生上下文切换。Linux为每个CPU维护了一个就绪队列,将活跃进程(即正在运行或正在等待CPU的进程)按照优先级和等待CPU的时间排序,然后选择最需要CPU的进程,也就是优先级最高和等待时间最长的进程来运行。

进程什么时候会调度到CPU上运行?

  • 为了保证所有CPU都可以公平调度,CPU时间被划分为一段段的时间片,这些时间片在轮流分配给各个进程。这样,当某个进程当时间片耗尽了,就会被系统挂起,切换到其他正在等待CPU的进程运行;
  • 进程在系统资源不足(如:内存不足)时,要等到资源满足后才可以运行,这个时候进程也会被挂起,并由系统调度其他进程运行;
  • 当进程通过睡眠函数sleep()这样的方法将自己挂起时,也会被重新调度;
  • 当有优先级更高的进程运行时,为了保证高优先级进程的运行,当前进程会被挂起,由高优先级进程运行;
  • 发生硬件中断时,CPU上的进程会被中断挂起,转而执行内核中的中断服务程序;

这几个场景是导致上下文切换性能问题的根本原因。

2、线程上下文切换

线程与进程最大的区别在于,线程是调度的基本单位,而进程则是资源拥有的基本单位。内核中的任务调度,调度的对象是线程;进程给线程提供了虚拟内存、全局变量等资源。所以,对于线程和进程可以理解为:

  • 当前只有一个线程时,可以认为线程等于进程;
  • 当进程拥有多个线程时,这些线程会共享相同的虚拟内存和全局变量资源。这些资源在上下文切换时是不需要更改的;
  • 线程也有自己的私有数据,比如:栈、寄存器等,这些在上下文切换时也是需要保存的。

所以线程上下文切换可以分为以下两种情况:

第一种,前后两个线程属于不同的进程;此时,因为资源不共享,所以切换过程就跟进程上下文切换是一样。

第二种,前后两个线程属于同一个进程;此时,因为虚拟内存是共享的,所以在切换时,虚拟内存这些资源就保持不懂,只需要切换线程的私有数据、寄存器等不共享的数据。

虽然同为上下文切换,同进程的线程切换要比多进程间的线程切换消耗更少的资源,而这,也正是多线程代替多进程的一个优势。

3、中断上下文切换

为了快速相应硬件的事件,中断处理会打断进程的正常调度和执行,转而调用中断处理程序,相应设备事件。在打断其他进程时,就需要将进程当前的状态保存下来,这样在中断结束后,进程仍然可以从原来的 状态恢复运行。

跟进程上下文切换不同,中断上下文切换并不涉及到进程的用户态。所以,即便中断过程打断了一个正处于用户态的进程,也不需要保存和恢复这个进程的虚拟内存、全局变量等用户态资源。中断上下文切换只包括内核态中断服务程序执行所需的状态,包括CPU寄存器、内核堆栈、硬件中断参数等。

对同一个CPU来说,中断处理比进程拥有更高的优先级,所以中断上下文切换并不会与进程上下文切换同时发生。

跟进程上下文切换一样,中断上下文切换也需要消耗CPU,切换次数过多也会消耗大量对CPU,甚至严重降低系统的整体性能。

2.3、查看系统的上下文切换情况

查看上下文切换可以使用vmstat工具,它是一个常用的系统性能分析工具,主要用来分析系统的内存使用情况,也常用来分析CPU上下文切换和中断的次数。

示例:5表示每隔5秒输出1组数据

各指标含义:

procs

r:等待访问处理器的进程总数。展示CPU运行饱和度,不包含IO

b:sleep 状态的进程数

memory:

swpd:交换区大小,大于0说明内存不足

free:未分配的内存

buff:缓冲区大小,一般只有几十M

cache:缓存大小,通常几个G

swap:

si(swap-in):每秒从交换区写入内存的大小(单位:kb/s)

so(swap-out):每秒从内存写到交换区的大小

io:

bi(blocks-in):每秒读取的块数(读磁盘)

bo(blocks-out):每秒写入的块数(写磁盘)

system:

in(system interrupts):每秒中断数,包括时钟中断

cs(context switches):每秒上下文切换数

cpu(us+sy+id+wa+st = 100):

us(user time):用户进程执行消耗cpu时间

sy(system time):系统进程消耗cpu时间

Id(idle):空闲时间(包括IO等待时间)

wa(wait I/O):等待IO时间

st(steal time):代表当系统运行在虚拟机中的时候,被其他虚拟机占用的 CPU时间

 

vmstat只给出了系统总体上下文切换情况,想要看每个进程的详细情况,需要使用pidstat.

命令:pidstat -w 5   (每隔5秒输出一组数据)

重点关注两列:

  • cswch(voluntary context switches):表示每秒自愿上下文切换的次数,指进程无法获取所需资源,导致的上下文切换;比如:I/O、内存等系统资源不足时
  • nvcswch(non voluntary context switches):表示每秒非自愿上下文切换的次数,指进程由于时间片已到等原因,被系统强制调度,进而发生的上下文切换;比如:大量进程都在竞争CPU时

2.4、案例分析

1、准备环境

系统:Ubuntu 18.04.2

机器配置:2CPU   2G内存

工具:sysbench  sysstat

2、执行步骤

1)观察空闲系统的上下文切换次数

  • 命令:vmstat 1 1 
  • 描述:间隔1秒后输出1组数据

cs(上下文切换):883次  in(中断):221次  r:1个 b:0

2)在第一个终端运行sysbench,模拟系统多线程调用瓶颈

  •   命令:sysbench --threads=10 --max-time=300 threads run
  • 描述:10个线程运行 5分钟基准测试

3)在第二个终端运行vmstat,观察上下文切换情况

  • 命令:vmstat 1
  • 描述:每隔1描述输出1组数据

需要关注几列数据的变化:

cs(上下文切换):从802升到200万+,存在大量的上下文切换;

r列:就绪队列的长度升到列9左右,超过了CPU(2个)个数,所以存在CPU竞争问题;

us列和sy列:这两列的CPU使用率加起来达到列100%,其中系统CPU使用率达到列83%,说明CPU主要被内核使用;

in列:终端 次数上升到1.8万+,说明中断处理也存在一定的问题;

从这些指标的变化,我们可以知道系统的就绪队列过长,导致了大量的上下文切换,而上下文切换导致了系统CPU的占用率升高。

但是还不知道引起这些指标升高的进程。我们可以借助pidstat看一下,执行下面命令。

命令:pidstat -w -u 1

描述:每隔一秒输出1组数据,-w 表示输出进程切换指标,-u表示输出cpu使用指标。

从pidstat输出我们可以看到,CPU使用率升高正是sysbench导致的,达到了100%,但是上下文切换来自其他进程,但是总的加起来上下文切换次数也比200万少很多。可以想一下这是为什么?

Linux调度的基本单位是线程,我们使用sysbench模拟的也是线程的调度,pidstat默认使用的是进程的指标数据,可以加上-t输出线程指标。使用下面命令:

命令:pidstat -wt 1

描述:每隔1秒输出1组数据,-wt 表示输出线程的上下文切换指标

从中我们可以看到 sysbench的子线程的上下文切换次数很多,这样我们就找到了引起上下文切换过多的原因。

但是我们在前面查找的过程中还发现一个问题,除了CPU使用率升高,上下文切换次数增多,中断次数也发生了很大的变化。但是还不清楚中断的源头!

中断只发生在内核,所以我们可以从/proc/interrupts这个文件读取中断次数。/proc是Linux的一个虚拟文件系统,用于内核空间与用户空间之间的通信。

命令:watch -d  cat /proc/interrupts

描述:-d 表示高亮显示变化的区域

通过观察可以发现变化最快的是重调度中断(RES),这个中断表示:唤醒空闲状态的CPU来调度新的任务运行。这是多处理器系统(SMP)中,调度器用来分散任务到不同CPU的机制,通常也被称为处理器间中断

所以前面我们看到的中断升高还是因为多任务的调度问题,跟前面上下文切换次数的分析结果一致。

回到最初的问题,每秒上下文切换多少才算是正常?

上下文切换次数取决于系统本身的CPU性能。如果系统的CPU上下文切换次数比较稳定,那么从数百到一万以内,都算正常。如果超过一万,或者上下文切换次数出现数量级增长,就有可能出现了性能问题。根据不同上下文切换的类型,可能有以下几个方面:

  • 自愿上下文切换变多,说明进程都在等待资源,可能发生了I/O等其他问题;
  • 非自愿上下文切换变多了,说明进程都在被强制调度,也就是都在抢CPU资源,说明CPU成了瓶颈;
  • 中断次数多了,说明CPU被中断处理程序占用,可以通过查看/proc/interrupts文件具体分析。

三、CPU使用率

3.1、CPU使用率介绍

Linux是一个多任务操作系统,将每个CPU的时间划分为很短的时间片,再通过调度器轮流分配给各个任务使用,因此造成了多任务同时运行的错觉。

为了维护CPU时间,Linux通过预先定义的节拍率(内核中表示为HZ),触发时间中断,并使用全局变量Jiffies记录了开机以来的节拍数。没发生一次中断,Jiffies的值就加1.

节拍率是内核的可配置选项,不同的系统可能不同。可以通过查询/boot/config内核选项来查看他的配置。我安装的系统中设置成了250,也就是每秒触发250次时间中断。

同时也正是因为节拍率HZ是内核选项,所以用户空间程序不能直接访问。为了方便用户空间程序,内核提供了一个用户空间节拍率USER_HZ,它固定为100,也就是1/100 秒。

Linux 通过 /proc 虚拟文件系统,向用户空间提供了系统内部状态的信息,而 /proc/stat 提供的就是系统的 CPU 和任务统计信息。

命令:cat /proc/stat |grep ^cpu

描述:查看CPU信息

第一例显示的是CPU编号,第一行没有编号的表示所有CPU的累加。其他列则表示不同场景下 CPU 的累加节拍数,它的单位是 USER_HZ,也就是 10 ms(1/100 秒),所以这其实就是不同场景下的 CPU 时间。具体各列如下:

  • user(缩写us):代表用户态 CPU 时间。不包括下面的 nice 时间,但是包括列guest时间。
  • nice(缩写ni):代表低优先级用户态CPU时间,也就是进程的nice值被调整为1~19之间时的CPU时间。nice值的取值范围是-20~19,数值越大,优先级越低。
  • system(缩写sys):代表内核态的CPU时间。
  • idle(缩写id):代表空闲时间,不包括等待I/O的时间(iowait)。
  • irq(缩写hi):代表处理硬中断的CPU时间。
  • softirq(缩写si):代表处理软中断的CPU时间。
  • steal(缩写st):代表当前系统运行在虚拟机中的时候,被其他虚拟机占用的CPU的时间。
  • guest(缩写guest):代表通过虚拟化运行其他操作系统的时间,也就是运行虚拟机的CPU时间。
  • guest_nice(缩写gnice):代表以低优先级运行虚拟机的时间。

我们通常所说的CPU使用率,就是除了空闲时间外的其他时间占总CPU时间的百分比。用公式表示如下:

根据这个公式我们可以直接算出CPU的使用率,但是根据/proc/stat 中的数据计算出的是开机以来的平均CPU使用率,没有太大参考价值。

通常为了计算 CPU 使用率,性能工具一般都会取间隔一段时间(比如 3 秒)的两次值,作差后,再计算出这段时间内的平均 CPU使用率,即:

这个公式,就是我们用各种性能工具所看到的 CPU 使用率的实际计算方法。

了解了CPU使用率计算公式,是不是每次都需要我们去根据公式计算CPU使用率呢?

没有必要,各种各样的性能分析工具已经帮我们计算好了。不过要注意的是,性能分析工具给出的都是间隔一段时间的平均 CPU 使用率所以要注意间隔时间的设置,特别是用多个工具对比分析时,你一定要保证它们用的是相同的间隔时间。

比如,对比一下 top 和 ps 这两个工具报告的 CPU 使用率,默认的结果很可能不一样,因为 top 默认使用 3 秒时间间隔,而 ps 使用的却是进程的整个生命周期。

3.2、怎么查看CPU使用率

查看CPU使用率常用的工具有top,这是最常用的性能分析工具。

  • top显示了系统总体的CPU 和内存使用情况,以及各个进程资源的使用情况。
  • ps则显示了每个进程资源的使用情况。

比如top命令输出如下:

第三行就是cpu使用率情况。top 默认显示的是所有 CPU 的平均值,这个时候你只需要按下数字 1 ,就可以切换到每个 CPU 的使用率了。

空白行之后是进程的实时信息,每个进程都有一个 %CPU 列,表示进程的 CPU 使用率。它是用户态和内核态 CPU 使用率的总和,包括进程用户空间使用的 CPU、通过系统调用执行的内核空间 CPU 、以及在就绪队列等待运行的 CPU。在虚拟化环境中,它还包括了运行虚拟机占用的 CPU。

top 并没有细分进程的用户态 CPU 和内核态 CPU。但是我们可以借助pidstat命令,它是专门分析每个进程 CPU 使用情况的工具。

比如,下面的 pidstat 命令,就间隔 1 秒展示了进程的 3 组 CPU 使用率,包括:

  • %user:用户态 CPU 使用率;
  • %system:内核态 CPU 使用率;
  • %guest:运行虚拟机 CPU 使用率;
  • %wait:等待 CPU 使用率;
  • %CPU:总的 CPU 使用率(%CPU)。

最后的 Average 部分,还计算了 5 组数据的平均值。

3.3、CPU使用率过高怎么办

了解了CPU相关各个指标的含义,知道了如何查看CPU使用率,那么当我们发现CPU使用过高怎么办?通过top我们能够找到使用率高的进程,可能我们又想查看引起CPU使用率升高的是代码中哪个具体的函数?这时,我们又该怎么做呢?

这里我们可以借助perf工具,perf 是 Linux 2.6.31 以后内置的性能分析工具。它以性能事件采样为基础,不仅可以分析系统的各种事件和内核性能,还可以用来分析指定应用程序的性能问题。

使用perf分析CPU性能,可以采用以下两种方式:

第一种常见用法是 perf top,类似于 top,它能够实时显示占用 CPU 时钟最多的函数或者指令,因此可以用来查找热点函数,使用界面如下所示:

输出结果中,第一行包含三个数据,分别是采样数(Samples)、事件类型(event)和事件总数量(Event count)。

另外,采样数需要我们特别注意。如果采样数过少(比如只有十几个)。那么下面的排序还有百分比就没有了什么太大的参考价值。

我们看一下输出列表中各列数据的含义。

  • 第一列Overhead,是该符号的性能事件在所有采样中的比例,用百分比表示;
  • 第二个列Shared,是该函数或指令所在的动态共享对象(Dynamic Shared Object),如内核、进程名、动态链接库名、内核模块名等。
  • 第三列Object,是动态共享对象的类型。比如[.]表示用户空间的可执行程序、或者动态链接库,而[k]则表示内核空间。
  • 最后一列Symbol是符号名,也就是函数名。当函数名未知时,用十六进制的地址来表示。

第二种用法,perf record和perf report。perf top虽然实时展示了系统的性能信息,但它的缺点是并不保存数据,也就无法用于离线或后续的分析。而perf record则提供了保存数据的功能,保存后的数据,需要你用perf report解析展示。

 

四、软中断

4.1、怎么理解软中断

中断是系统用来相应硬件设备请求的一种机制,它会打断进程的正常调度和执行,然后调用内核中的中断处理程序来响应设备的请求。

中断其实是一种异步的处理机制,可以提高系统的并发处理能力。

由于中断会打断其他程序的运行,所以为了减少对正常进程运行调度的影响,中断处理程序就需要尽可能快地运行。如果中断本身要做的事情不多,那么处理起来也不会有太大问题;但如果中断要处理的事情很多,中断服务程序就有可能要运行很长时间。

特别是,中断处理程序在响应中断时,还会临时关闭中断。这就会导导致上一次中断处理完成之前,其他中断都不能响应,也就是说中断有可能会丢失。

为了解决软中断处理程序执行过长和中断丢失的问题,Linux将中断处理过程分成了两个阶段,上半部和下半部:

  • 上半部用来快速处理中断,它在中断禁止模式下运行,主要处理硬件紧密相关的或时间敏感的工作;
  • 下半部用来延迟处理上半部未完成的工作,通常以内核线程的方式运行。

可以举个例子,比如网卡接收数据包。网卡接收到数据包后,会通过硬件中断的方式,通知内核有新的数据到了。这时,内核就应该调用中断处理程序来响应它。

这时对于上半部来说,其实就是要把网卡的数据读到内存中,然后更新一下硬件寄存器的状态(表示数据已经准备好了),最后再发送一个软中断信号,通知下半部做进一步的处理。

而下半部被软中断唤醒后,需要从内存中找到网络数据,再按照网络协议栈,对数据进行逐层解析和处理,直到把它送给应用程序。

所以,这两个阶段可以这样理解:

  • 上半部直接处理硬件请求,也就是我们常说对硬中断,特点是快速执行;
  • 而下半部则是由内核触发,也就是我们常说的软中断,特点是延迟执行。

实际上,上半部会打断 CPU 正在执行的任务,然后立即执行中断处理程序。而下半部以内核线程的方式执行,并且每个 CPU都对应一个软中断内核线程,名字为 “ksoftirqd/CPU编号”,比如说, 0 号 CPU 对应的软中断内核线程的名ksoftirqd/0。

4.2、查看软中断和内核线程

proc文件系统,是一种内核空间和用户空间互相通信的机制,可以用来查看内核的数据结构,或者用来动态修改内核的配置。其中:

  • /proc/softirqs提供来软中断的运行情况;
  • /proc/interrupts提供来硬中断的运行情况;

运行下面的命令可以查看文件/proc/softirqs文件的内容。就可以看到各种类型软中断在不同 CPU 上的累积运行次数:

cat /proc/softirqs

                    CPU0       CPU1

          HI:          0          0

       TIMER:     811613    1972736

      NET_TX:         49          7

      NET_RX:    1136736    1506885

       BLOCK:          0          0

    IRQ_POLL:          0          0

     TASKLET:     304787       3691

       SCHED:     689718    1897539

     HRTIMER:          0          0

         RCU:    1330771    1354737

 

查看/proc/softirqs文件内容时,需要特别注意两点:

第一、要注意软中断的类型,也就是第一列的内容。如NET_RX 表示网络接收中断,而 NET_TX 表示网络发送中断。

第二、要注意同一种软中断在不同 CPU 上的分布情况,也就是同一行的内容。正常情况下,同一种中断在不同 CPU 上的累积次数应该差不多。

从查看的信息中看到TASKLET在不同 CPU 上的分布并不均匀。TASKLET 是最常用的软中断实现机制,每个 TASKLET 只运行一次就会结束 ,并且只在调用它的函数所在的 CPU 上运行。

因此,使用 TASKLET 特别简便,当然也会存在一些问题,比如说由于只在一个 CPU 上运行导致的调度不均衡,再比如因为不能在多个 CPU 上并行运行带来了性能限制。

另外,刚刚提到过,软中断实际上是以内核线程的方式运行的,每个CPU 都对应一个软中断内核线程,这个软中断内核线程就叫做 ksoftirqd/CPU 编号。

root@ubuntu:~# ps aux|grep softirq

root         9  0.0  0.0      0     0 ?        S    10:59   0:00 [ksoftirqd/0]

root        18  0.0  0.0      0     0 ?        S    10:59   0:00 [ksoftirqd/1]

root      6654  0.0  0.0  21536  1060 pts/1    S+   11:21   0:00 grep --color=auto softirq

注意,这些线程的名字外面都有中括号,这说明 ps 无法获取它们的命令行参数(cmline)。一般来说,ps 的输出中,名字括在中括号里的,一般都是内核线程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值