美文网首页
性能瓶颈--CPU(平均负载)

性能瓶颈--CPU(平均负载)

作者: OOM_Killer | 来源:发表于2019-03-21 00:01 被阅读0次

    操作系统的发展

    单进程时代

    这个时候的每一个程序就是一个进程,直到这个进程运行完毕,才能接着运行下一个进程,这是单进程的串行时代。但这有个问题,首先,如果进程需要接收数据,阻塞了,那么cpu就在白白浪费,为了避免这种十分不必要的浪费,怎么办呢,多进程。

    多进程时代

    为了避免单进程串行运行阻塞导致的cpu浪费,多进程的解决方式是如果一个进程阻塞了,那么cpu会切换到另外等待执行的进程上。这样就完美了。
    但是日子久了,程序员发现,多进程有个问题啊,进程拥有的资源太多了,cpu在切换的时候把时间都花在了资源的创建,切换,销毁。真正用cpu在计算的时间太少了,cpu还是利用不起来啊。尤其是再是个多核cpu,进程一会在这个cpu上一会在那个cpu上,更浪费宝贵的cpu。看来得需要一个轻量级的进程了,

    线程时代

    在期盼中,线程诞生了,一个进程中可以运行多个线程,由于线程的上下文切换是十分便捷的。进程为线程提供了虚拟内存全局变量。线程的切换只需要将自己的一些栈和寄存器切换保存就行了。但是多线程还是很坑啊,具体看看为什么Nginx采用多进程单线程的原因就知道了。这里不多做说明。

    新兴的协程

    既然线程依旧还有问题,程序员不放弃啊,协程诞生了,运行于线程之上。它非常轻量,一个goroutine只占几KB,并且这几KB就足够goroutine运行完,要想一个线程是要占用几MB的。协程本身就是运行在一组线程之上,不需要频繁的创建、销毁线程,而是对线程的复用。

    cpu的性能诊断

    说了这么多,cpu的瓶颈究竟是从哪来的呢?什么会造成cpu的性能瓶颈。其实要不就是任务太重,要不就用的不当。(可能说了一句废话)。要想知道为什么cpu会产生瓶颈,还是得了解cpu的运行原理。又回到了最枯燥的地方。
    应用程序在服务器上运行的大致是这样的一个架构。应用层,运行依赖的环境,操作系统和硬件。


    程序运行架构.png

    下面这张是经典的性能分析工具图谱。其实看到这张图,好多人可能以为又是要开始飘了。。。


    linux_perf_tools_full.png

    cpu性能指标

    • 平均负载
    • cpu使用率
    • cpu上下文切换 (进程,线程)
    • 中断 (硬中断,软中断)
      其实 cpu 的性能瓶颈就是围绕着这几点展开的

    平均负载

    • 含义:平均负载指处于可运行状态不可中断状态 的平均进程数,运行队列中的活跃进程数的平均值。所以其不仅有正在使用CPU的进程,还包括等待CPU和等待IO的进程。
    R:Running or Runnable状态,表示进程在CPU的就绪队列中,正在运行或等待运行
    D:Disk Sleep缩写,表示不可中断状态睡眠,一般表示进程正在与硬件交互,并且交互过程中不允许其他进程打断(进程不响应异步信号),
    举例:
        比如执行read系统调用对某个设备文件进行读操作时需要用D来标注,避免进程与设备交互的过程被打断。
        D状态的进程是不响应信号量的,因为已经完全陷入了内核态,想要杀死D状态的进程,要么重启,要么满足他请求的资源。
    Z:Zombie的缩写,指的是实际进程已经结束但父进程并未回收他的资源(PID,进程描述符等)
    S:Interrruptible Sleep的缩写,可中断状态睡眠,进程因等待某个事件而被挂起。当事件发生时,进程将会被添加到就绪队列等待调度,进程状态更新为R
    I:idle缩写用在不可中断睡眠的内核线程上,硬件交互导致的不可中断进程为D,但某些内核线程可能没有任何负载,使用Idle来区分这种状态,注意D状态会导致平均负载升高,但是I状态的进程不会。(例如等待socket连接,等待信号量)
    T:进程停止状态,可以发送SIGSTOP信号来停止进程。这个暂停的进程可以通过发送SIGCONT信号让进城继续运行
    X:死亡状态,这个状态指示一个返回状态,在任务列表无法看到这种状态的进程。
    

    下图是进程之间的转换关系,同样是引用原博主的。


    process_state.png

    一般来说,cpu的负载率需要小于服务器的cpu核数,这样才是正常的。不过需要注意的是cpu的负载和cpu的使用率是没有直接关系的,cpu的使用率是反应cpu花在计算上的时间。而平均负载只是统计处于R和D状态的进程。

    对于CPU密集型应用,进程占用了大量的CPU会导致平均负载升高,这是CPU使用率和平均负载的变化是一致的(处于R状态的进程多)
    对于I/O密集型应用,进程大部分时间在等待I/O,虽然会导致平均负载升高,然而CPU使用率却不会升高(处于D状态的进程多)
    对于大量等待CPU的的进程调度也会导致平均负载升高,此时的CPU使用率也会比较高。
    
    cpu密集场景模拟

    压缩和解压、加密和解密是十分消耗CPU的。所以通过这样的方法模拟cpu密集的场景。

    $ cat /dev/urandom | gzip -9 | gzip -d | gzip -9 | gzip -d > /dev/null
    

    执行后观察load

     $ uptime
     23:20:42 up 36 min,  5 users,  load average: 2.38, 2.14, 1.37
    

    load 是升高了,但似乎不是很高(我的虚拟机是4核的)
    看看cpu的使用率。

    $ mpstat  -P ALL 1
    平均时间:  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
    平均时间:  all   73.54    0.00   24.28    0.00    0.00    0.04    0.00    0.00    0.00    2.14
    平均时间:    0   74.46    0.00   23.08    0.00    0.00    0.00    0.00    0.00    0.00    2.47
    平均时间:    1   70.95    0.00   26.42    0.00    0.00    0.00    0.00    0.00    0.00    2.63
    平均时间:    2   73.19    0.00   24.64    0.00    0.00    0.00    0.00    0.00    0.00    2.17
    平均时间:    3   75.32    0.00   23.10    0.00    0.00    0.29    0.00    0.00    0.00    1.29
    
    

    可以看到 %usr 占用很多,这证明cpu被利用在用户态,且被充分利用计算。想要看到底是哪个进程在消耗cpu,就需要pidstat 了。

    $ pidstat -u 1
    平均时间:   UID       PID    %usr %system  %guest   %wait    %CPU   CPU  Command
    平均时间:   124      1064    0.96    0.96    0.00    0.00    1.92     -  mongod
    平均时间:  1001      6762    0.00   12.50    0.00   10.58   12.50     -  cat
    平均时间:  1001      6763   70.19    4.81    0.00   21.15   75.00     -  gzip
    平均时间:  1001      6764   10.58    0.96    0.00    7.69   11.54     -  gzip
    平均时间:  1001      6765   63.46    4.81    0.00   26.92   68.27     -  gzip
    平均时间:  1001      6766   10.58    0.00    0.00   18.27   10.58     -  gzip
    

    可以看到是gzip在消耗cpu。

    I/O 密集型场景模拟

    使用压测工具模拟读写临时文件

    $ uptime
    23:38:37 up 54 min,  5 users,  load average: 3.36, 2.68, 1.15
    

    load 是在上升的。
    使用mpstat看看。

    $ mpstat -P ALL 1
    平均时间:  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
    平均时间:  all    1.15    0.36   19.76    3.71    0.00    4.61    0.00    0.00    0.00   70.41
    平均时间:    0    1.53    0.42   14.90    4.65    0.00   10.47    0.00    0.00    0.00   68.03
    平均时间:    1    0.87    0.39   23.15    3.66    0.00    0.23    0.00    0.00    0.00   71.70
    平均时间:    2    1.40    0.00   24.84    2.91    0.00    3.65    0.00    0.00    0.00   67.20
    平均时间:    3    0.84    0.65   15.83    3.67    0.00    4.44    0.00    0.00    0.00   74.57
    

    明显iowait在升高。
    使用pidstat查看究竟是哪个进程导致的iowait这么高。

    $ pidstat -u 1
    平均时间:   UID       PID    %usr %system  %guest   %wait    %CPU   CPU  Command
    平均时间:     0         7    0.00    2.75    0.00    0.92    2.75     -  ksoftirqd/0
    平均时间:     0       359    0.00    1.83    0.00    0.00    1.83     -  kworker/0:1H
    平均时间:   124      1064    0.92    0.00    0.00    0.00    0.92     -  mongod
    平均时间:     0      1281    2.75    0.00    0.00    0.00    2.75     -  vmtoolsd
    平均时间:  1001      2553    1.83    0.00    0.00    0.00    1.83     -  vmtoolsd
    平均时间:     0      6792    0.00   92.66    0.00    1.83   92.66     -  kworker/u256:0
    平均时间:     0      7565    0.00    0.92    0.00    4.59    0.92     -  stress-ng-io
    平均时间:     0      7566    0.92   90.83    0.00    0.00   91.74     -  stress-ng-hdd
    平均时间:     0      7592    0.00    3.67    0.00    0.00    3.67     -  pidstat
    
    大量进程的场景

    还是使用压测神器 stress-ng。启用 20个进程进行运算圆周率 Π。

    $ stress-ng -c 20 --cpu-method pi
    stress-ng: info:  [7624] defaulting to a 86400 second (1 day, 0.00 secs) run per stressor
    stress-ng: info:  [7624] dispatching hogs: 20 cpu
    

    uptime 已经到达20左右

    $ uptime
     23:48:39 up  1:04,  5 users,  load average: 18.26, 8.87, 4.82
    

    运行mpstat查看cpu总体资源。

    $ mpstat 1
    Linux 4.15.0-46-generic (BattleAngel)   2019年03月20日     _x86_64_    (4 CPU)
    
    23时49分32秒  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
    23时49分33秒  all   99.75    0.00    0.25    0.00    0.00    0.00    0.00    0.00    0.00    0.00
    23时49分34秒  all  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
    

    几乎都是花在 %usr 上。
    看看pidstat。

    $ pidstat -u 1
    平均时间:   UID       PID    %usr %system  %guest   %wait    %CPU   CPU  Command
    平均时间:  1001      2553    0.96    0.00    0.00    0.00    0.96     -  vmtoolsd
    平均时间:     0      7625   18.27    0.00    0.00   80.77   18.27     -  stress-ng-cpu
    平均时间:     0      7626   23.08    0.00    0.00   74.04   23.08     -  stress-ng-cpu
    平均时间:     0      7627   19.23    0.00    0.00   79.81   19.23     -  stress-ng-cpu
    平均时间:     0      7628   18.27    0.00    0.00   81.73   18.27     -  stress-ng-cpu
    平均时间:     0      7629   20.19    0.00    0.00   78.85   20.19     -  stress-ng-cpu
    平均时间:     0      7630   20.19    0.00    0.00   79.81   20.19     - 
    

    20个进程在争抢4个cpu。进程们都在等待cpu的时间(就是%wait),自己正真运行的时间都不足25%

    得出的结论是,cpu的负载load高的原因并不只是cpu使用率高导致的。高I/O 也有可能导致load增高。

    前面说到了,cpu的瓶颈并不只局限于 load,还有上下文切换,中断等。这个后面会在做分析。这里还是觉得 倪鹏飞老师很厉害,https://time.geekbang.org/column/article/86157

    相关文章

      网友评论

          本文标题:性能瓶颈--CPU(平均负载)

          本文链接:https://www.haomeiwen.com/subject/uhxsmqtx.html