Linux性能优化
CPU
系统层面的一些优化手段:
- CPU 绑定:把进程绑定到一个或者多个 CPU 上,可以提高 CPU 缓存的命中率,减少跨 CPU 调度带来的上下文切换问题
- CPU 独占:跟 CPU 绑定类似,进一步将 CPU 分组,并通过 CPU 亲和性机制为其分配进程。这样,这些 CPU 就由指定的进程独占,换句话说,不允许其他进程再来使用这些 CPU
- 优先级调整:使用 nice 调整进程的优先级,正值调低优先级,负值调高优先级。,适当降低非核心应用的优先级,增高核心应用的优先级,可以确保核心应用得到优先处理
- 为进程设置资源限制:使用 Linux cgroups 来设置进程的 CPU 使用上限,可以防止由于某个应用自身的问题,而耗尽系统资源
- NUMA(Non-Uniform Memory Access)优化:支持 NUMA 的处理器会被划分为多个 node,每个 node 都有自己的本地内存空间。NUMA 优化,其实就是让 CPU 尽可能只访问本地内存
- 中断负载均衡:无论是软中断还是硬中断,它们的中断处理程序都可能会耗费大量的 CPU。开启 irqbalance 服务或者配置 smp_affinity,就可以把中断处理过程自动负载均衡到多个 CPU 上。
平均负载
平均负载是指单位时间内,系统处于可运行状态和不可中断状态的平均进程数,也就是平均活跃进程数,和 CPU 使用率并没有直接关系
在只有 2 个 CPU 的系统上,平均负载为 2 代表所有的 CPU 都刚好被完全占用,大量 IO 会导致平均负载变高,但 CPU 使用率却不一定高
top 命令展示的三个值,分别为 1 分钟、5 分钟、15 分钟内的平均负载
上下文切换
vmstat 可以查看系统整体上下文切换情况:
- cs(context switch)是每秒上下文切换的次数
- in(interrupt)则是每秒中断的次数
- r(Running or Runnable)是就绪队列的长度,也就是正在运行和等待 CPU 的进程数
- b(Blocked)则是处于不可中断睡眠状态的进程数
pidstat 则可以查看单个进程的上下文切换情况,cswch ,表示每秒自愿上下文切换(voluntary context switches)的次数,另一个则是 nvcswch ,表示每秒非自愿上下文切换(non voluntary context switches)的次数
- 自愿上下文切换:是指进程无法获取所需资源,导致的上下文切换。比如说, I/O、内存等系统资源不足时,就会发生自愿上下文切换。
- 非自愿上下文切换:是指进程由于时间片已到等原因,被系统强制调度,进而发生的上下文切换,大量进程都在争抢 CPU 时,就容易发生非自愿上下文切换
中断处理也需要上下文切换,可以通过 /proc/interrupts
查看中断最多的类型
CPU使用率
通过 top 可以查看 CPU 在不同场景下的运行时间:
- user(通常缩写为 us),代表用户态 CPU 时间。注它不包括下面的 nice 时间,但包括了 guest 时间
- nice(通常缩写为 ni),代表低优先级用户态 CPU 时间,也就是进程的 nice 值被调整为 1-19 之间时的 CPU 时间,nice 可取值范围是 -20 到 19,数值越大,优先级反而越低
- system(通常缩写为 sys),代表内核态 CPU 时间
- idle(通常缩写为 id),代表空闲时间。它不包括等待 I/O 的时间(iowait)
- iowait(通常缩写为 wa),代表等待 I/O 的 CPU 时间
- irq(通常缩写为 hi),代表处理硬中断的 CPU 时间
- softirq(通常缩写为 si),代表处理软中断的 CPU 时间
- steal(通常缩写为 st),代表当系统运行在虚拟机中的时候,被其他虚拟机占用的 CPU 时间
- guest(通常缩写为 guest),代表通过虚拟化运行其他操作系统的时间,也就是运行虚拟机的 CPU 时间
- guest_nice(通常缩写为 gnice),代表以低优先级运行虚拟机的时间
为了在具体进程找到占用 CPU 时钟最多的函数或者指令,可以通过 perf top 查看函数名或函数地址
软中断
Linux 将中断处理过程分成了两个阶段,也就是上半部和下半部:
- 上半部用来快速处理中断,它在中断禁止模式下运行,主要处理跟硬件紧密相关的或时间敏感的工作
- 下半部用来延迟处理上半部未完成的工作,通常以内核线程的方式运行
网卡接收到数据包后,会通过硬件中断的方式通知内核有新的数据到了 对上半部来说,既然是快速处理,其实就是要把网卡的数据读到内存中,然后更新一下硬件寄存器的状态(表示数据已经读好了),最后再发送一个软中断信号,通知下半部做进一步的处理 下半部被软中断信号唤醒后,需要从内存中找到网络数据,再按照网络协议栈,对数据进行逐层解析和处理,直到把它送给应用程序
上半部会打断 CPU 正在执行的任务,然后立即执行中断处理程序。而下半部以内核线程的方式执行,并且每个 CPU 都对应一个软中断内核线程,名字为 “ksoftirqd/CPU 编号”
- /proc/softirqs 提供了软中断的运行情况
- /proc/interrupts 提供了硬中断的运行情况
当软中断事件的频率过高时,内核线程也会因为 CPU 使用率过高而导致软中断处理不及时,进而引发网络收发延迟、调度缓慢等性能问题
内存
通过 free 可以查看整体内存使用情况:
- total 是总内存大小
- used 是已使用内存的大小,包含了共享内存
- free 是未使用内存的大小
- shared 是共享内存的大小
- buff/cache 是缓存和缓冲区的大小
- available 是新进程可用内存的大小,不仅包含未使用内存,还包括了可回收的缓存
通过 top 查看具体进程的内存情况:
- VIRT 是进程虚拟内存的大小,只要是进程申请过的内存,即便还没有真正分配物理内存,也会计算在内
- RES 是常驻内存的大小,也就是进程实际使用的物理内存大小,但不包括 Swap 和共享内存
- SHR 是共享内存的大小,比如与其他进程共同使用的共享内存、加载的动态链接库以及程序的代码段等
- %MEM 是进程使用物理内存占系统总内存的百分比
分配与回收
malloc()
对小块内存(小于 128K),C 标准库使用 brk() 来分配,也就是通过移动堆顶的位置来分配内存。这些内存释放后并不会立刻归还系统,而是被缓存起来,这样就可以重复使用,brk() 方式的缓存,可以减少缺页异常的发生,提高内存访问效率
而大块内存(大于 128K),则直接使用内存映射 mmap() 来分配,也就是在文件映射段找一块空闲内存分配出去
当进程通过 malloc() 申请内存后,内存并不会立即分配,而是在首次访问时,才通过缺页异常陷入内核中分配内存
buff/cache
buff 是对磁盘读写数据的缓存,而 cache 是对文件读写数据的缓存
在读写普通文件时,会经过文件系统,由文件系统负责与磁盘交互;而读写磁盘或者分区时,就会跳过文件系统,也就是所谓的裸I/O
swap
swap 是一种虚拟内存交换到磁盘的机制
有一个专门的内核线程用来定期回收内存,也就是 kswapd0
kswapd0 定义了三个内存阈值:页最小阈值(pages_min)、页低阈值(pages_low)和页高阈值(pages_high)
- 剩余内存小于页最小阈值,说明进程可用内存都耗尽了,只有内核才可以分配内存
- 剩余内存落在页最小阈值和页低阈值中间,说明内存压力比较大,剩余内存不多了。这时 kswapd0 会执行内存回收,直到剩余内存大于高阈值为止
- 剩余内存落在页低阈值和页高阈值中间,说明内存有一定压力,但还可以满足新内存请求
- 剩余内存大于页高阈值,说明剩余内存比较多,没有内存压力
NUMA 下,多个处理器被划分到不同 Node 上,且每个 Node 都拥有自己的本地内存空间,每个 Node 会有自己的 swap
Linux 提供了一个 /proc/sys/vm/swappiness 选项,用来调整使用 Swap 的积极程度:swappiness 的范围是 0-100,数值越大,越积极使用 Swap,也就是更倾向于回收匿名页;数值越小,越消极使用 Swap,也就是更倾向于回收文件页
- 对文件页的回收,是直接回收缓存,或者把脏页写回磁盘后再回收
- 对匿名页的回收,是通过 Swap 机制,把它们写入磁盘后再释放内存