Linux性能优化

CPU

系统层面的一些优化手段:

一些分析诊断工具

平均负载

平均负载是指单位时间内,系统处于可运行状态和不可中断状态的平均进程数,也就是平均活跃进程数,和 CPU 使用率并没有直接关系

在只有 2 个 CPU 的系统上,平均负载为 2 代表所有的 CPU 都刚好被完全占用,大量 IO 会导致平均负载变高,但 CPU 使用率却不一定高

top 命令展示的三个值,分别为 1 分钟、5 分钟、15 分钟内的平均负载

上下文切换

vmstat 可以查看系统整体上下文切换情况:

pidstat 则可以查看单个进程的上下文切换情况,cswch ,表示每秒自愿上下文切换(voluntary context switches)的次数,另一个则是 nvcswch ,表示每秒非自愿上下文切换(non voluntary context switches)的次数

中断处理也需要上下文切换,可以通过 /proc/interrupts 查看中断最多的类型

CPU使用率

通过 top 可以查看 CPU 在不同场景下的运行时间:

为了在具体进程找到占用 CPU 时钟最多的函数或者指令,可以通过 perf top 查看函数名或函数地址

软中断

Linux 将中断处理过程分成了两个阶段,也就是上半部和下半部:

网卡接收到数据包后,会通过硬件中断的方式通知内核有新的数据到了 对上半部来说,既然是快速处理,其实就是要把网卡的数据读到内存中,然后更新一下硬件寄存器的状态(表示数据已经读好了),最后再发送一个软中断信号,通知下半部做进一步的处理 下半部被软中断信号唤醒后,需要从内存中找到网络数据,再按照网络协议栈,对数据进行逐层解析和处理,直到把它送给应用程序

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

当软中断事件的频率过高时,内核线程也会因为 CPU 使用率过高而导致软中断处理不及时,进而引发网络收发延迟、调度缓慢等性能问题

内存

通过 free 可以查看整体内存使用情况:

通过 top 查看具体进程的内存情况:

mindmap
  内存性能指标
    系统内存指标
      已用内存
      剩余内存
      可用内存
      缺页异常
        主缺页异常
        次缺页异常
      缓存/缓冲区
        使用量
        命中率
      Slabs
    进程内存指标
      虚拟内存(VSS)
      常驻内存(RSS)
      按比例分配共享内存后的物理内存(PSS)
      独占内存(USS)
      共享内存
      SWAP内存
      缺页异常
        主缺页异常
        次缺页异常
    SWAP
      已用空间
      剩余空间
      换入速度
      换出速度

一些分析诊断工具

系统层面的优化手段:

分配与回收

malloc()

对小块内存(小于 128K),C 标准库使用 brk() 来分配,也就是通过移动堆顶的位置来分配内存。这些内存释放后并不会立刻归还系统,而是被缓存起来,这样就可以重复使用,brk() 方式的缓存,可以减少缺页异常的发生,提高内存访问效率

而大块内存(大于 128K),则直接使用内存映射 mmap() 来分配,也就是在文件映射段找一块空闲内存分配出去

当进程通过 malloc() 申请内存后,内存并不会立即分配,而是在首次访问时,才通过缺页异常陷入内核中分配内存

buff/cache

buff 是对磁盘读写数据的缓存,而 cache 是对文件读写数据的缓存

在读写普通文件时,会经过文件系统,由文件系统负责与磁盘交互;而读写磁盘或者分区时,就会跳过文件系统,也就是所谓的裸I/O

swap

swap 是一种虚拟内存交换到磁盘的机制

有一个专门的内核线程用来定期回收内存,也就是 kswapd0

kswapd0 定义了三个内存阈值:页最小阈值(pages_min)、页低阈值(pages_low)和页高阈值(pages_high)

NUMA 下,多个处理器被划分到不同 Node 上,且每个 Node 都拥有自己的本地内存空间,每个 Node 会有自己的 swap

Linux 提供了一个 /proc/sys/vm/swappiness 选项,用来调整使用 Swap 的积极程度:swappiness 的范围是 0-100,数值越大,越积极使用 Swap,也就是更倾向于回收匿名页;数值越小,越消极使用 Swap,也就是更倾向于回收文件页

IO

mindmap
  IO 性能指标
    磁盘
      使用率
      IOPS
      吞吐量
      响应时间
      缓冲区
      相关因素
        读写类型(如顺序还是随机)
        读写比例
        读写大小
        存储类型(如RAID级别、本地还是网络)
    文件系统
      存储空间容量、使用量以及剩余空间
      索引节点容量、使用量以及剩余量
      缓存
        页缓存
        目录项缓存
        索引节点缓存
        具体文件系统缓存(如ext4的缓存)
      IOPS(文件IO)
      响应时间(延迟)
      吞吐量(B/s)

一些优化手段:

容量

Linux 通过 inode 记录文件的元数据

可以通过 df -i 查看 inode 所占用的空间

缓存

内核使用 Slab 机制,管理目录项和 inode 的缓存,可以通过 /proc/slabinfo 查看这个缓存

IO栈

由上到下分为三个层次,分别是:

磁盘性能指标

在数据库、大量小文件等这类随机读写比较多的场景中,IOPS 更能反映系统的整体性能;而在多媒体等顺序读写较多的场景中,吞吐量才更能反映系统的整体性能

网络

网络包的接收流程:当一个网络帧到达网卡后,网卡会通过 DMA 方式,把这个网络包放到收包队列中,通过硬中断告诉中断处理程序已经收到了网络包,接着,网卡中断处理程序会为网络帧分配内核数据结构(sk_buff),并将其拷贝到 sk_buff 缓冲区中;然后再通过软中断,通知内核收到了新的网络帧,内核协议栈从缓冲区中取出网络帧,并通过网络协议栈,从下到上逐层处理这个网络帧

网络包发送流程:内核协议栈处理过的网络包被放到发包队列后,会有软中断通知驱动程序发包队列中有新的网络帧需要发送。驱动程序通过 DMA ,从发包队列中读出网络帧,并通过物理网卡把它发送出去

性能指标

通过 ifconfig 可以看到网卡网络收发的字节数、包数、错误数以及丢包情况:

netstat 可以看到套接字的接收队列(Recv-Q)和发送队列(Send-Q)

当套接字处于连接状态(Established)时,Recv-Q 表示套接字缓冲还没有被应用程序取走的字节数(即接收队列长度)。而 Send-Q 表示还没有被远端主机确认的字节数(即发送队列长度)。

当套接字处于监听状态(Listening)时,Recv-Q 表示全连接队列的长度。而 Send-Q 表示全连接队列的最大长度。全连接是指服务器收到了客户端的 ACK,完成了 TCP 三次握手,然后就会把这个连接挪到全连接队列中,半连接是指还没有完成 TCP 三次握手的连接

netstat -s 可以查看协议栈各层的统计信息

工作模型

主进程 + 多个 worker 子进程:主进程执行 bind() + listen() 后,创建多个子进程;然后在每个子进程中,都通过 accept() 或 epoll_wait() ,来处理相同的套接字

监听到相同端口的多进程模型:所有的进程都监听相同的接口,并且开启 SO_REUSEPORT 选项,由内核负责将请求负载均衡到这些监听进程中去

NAT

NAT 基于 Linux 内核的 conntrack 连接跟踪机制来实现,Linux 内核需要为 NAT 维护状态,维护状态也带来了很高的性能成本

网络层

路由和转发的角度

调整 MTU 大小:很多网络设备都支持巨帧,可以把 MTU 调大为 9000,提升网络吞吐量

为了避免 ICMP 主机探测、ICMP Flood 等各种网络问题:可以禁止 ICMP 协议,即设置 net.ipv4.icmp_echo_ignore_all = 1,还可以禁止广播 ICMP,即设置 net.ipv4.icmp_echo_ignore_broadcasts = 1

链路层

由于网卡收包后调用的中断处理程序(特别是软中断),需要消耗大量的 CPU。所以,将这些中断处理程序调度到不同的 CPU 上执行,就可以显著提高网络吞吐量

将原先内核中通过软件处理的功能卸载到网卡中,通过硬件来执行:

对于网络接口本身:

内核线程

Linux 在启动过程中 2 号进程为 kthreadd 进程,在内核态运行,用来管理内核线程

ps -f --ppid 2 -p 2