Linux性能优化笔记
Optimizing Linux Performance: A Hands-On Guide to Linux Performance Tools
1 性能追踪建议
记录一切可以搜集的信息,即使其中的一些信息在当时看起来没有太大的用途。
2 性能工具:系统CPU
在Linux下,进程有可运行和阻塞两种状态。可运行进程有分为两类:已经获得CPU时间正在运行的一类,和等待CPU时间的一类。在Linux系统中,运行队列长度就是系统中可运行状态的进程数量(平均值)。被阻塞的进程也可以分为两类:被I/O阻塞的进程,以及被系统调用阻塞的进程。一个CPU一次只能执行一个进程。在进程调度时,CPU需要把当前进程的信息保存起来,从内存中加载下一个进程的信息。这个过程叫做上下文切换。上下文切换会导致CPU中的高速缓存和流水线刷新,对性能有负面影响。为了公平的分配CPU时间,Linux会周期性的中断进程。通常,这种周期性上下文切换的数据可以作为考察系统上下文切换数量的基准:
cat /proc/interrupts | grep timer; sleep 10; cat /proc/interrupts | grep timer;
如果系统中断频率高于定时器的频率,说明进程经常陷入I/O等待或系统调用(比如休眠)等待之中。
在任意时刻,CPU可以处于以下几种状态: -空闲。CPU没有任务需要执行。 -执行用户代码,即“用户”时间。 -执行内核代码,即“系统”时间。 -执行低优先级的用户代码,即“nice”时间。 -等待I/O(磁盘或网络),即“iowait”时间。 -处理硬中断,即irq。 -处理软终端,softirq。
vmstat命令提供了系统CPU统计信息:
vmstat [-n] [-s] [delay [count]]
| -n | 不再重复显示列标题 | | -s | 输出系统启动以来的累计数据 | | delay | 采样间隔 |
vmstat显示的各列如下:
| r | 运行队列长度 | | b | 等待I/O操作的进程数 | | forks | 创建新进程的次数 | | in | 系统中断次数 | | cs | 系统上下文切换次数 | | us | 用户代码消耗的时间占比,即用户时间和nice时间之和 | | sy | 系统代码消耗的时间占比,即系统时间、irq和softirq时间之和 | | wa | 等待I/O的时间占比 | | id | 空闲时间占比 |
top命令也提供了CPU信息:
top [-d delay] [-n iter] [-i] [-b]
| -d delay | 更新间隔 | | -n iteration | 迭代次数 | | -i | 显示空闲进程 | | -b | 以批处理方式运行 |
top显示的各列如下:
| us | 用户时间 | | sy | 系统时间 | | ni | nice时间 | | id | 空闲时间 | | wa | 等待I/O的时间 | | hi | irq时间 | | si | softirq时间 | | load average | 系统平均负载 | | %CPU | 特定线程CPU占比 | | PRI | 进程优先级 | | NI | 进程nice值 | | WCHAN | 等待在哪个系统调用中 | | TIME | 进程消耗的CPU总时间(用户时间和系统时间) | | COMMAND | 进程执行的命令 | | s | 进程状态:睡眠S、运行R、僵尸Z、不可中断睡眠D、跟踪T |
另外一个常用的命令是procinfo:
procinfo [-f] [-b] [-D] [-n sec] [-F file]
| -f | 全屏 | | -d | 显示变化量 | | -D | 显示总量 | | -n sec | 采用间隔 | | -F file | 输出到文件 |
top输出的各列为:
| user | CPU消耗的总用户时间 | | nice | CPU消耗的总nice时间 | | system | CPU消耗的总系统时间 | | idle | CPU消耗的总空闲时间 | | irq 0-N | irq编号,已启动次数即内存程序 |
mpstat命令可以查看指定CPU上的时间分配:
mpstat [-P {cpu|ALL}] [delay [count]]
| -P | 监视哪个CPU,从0开始 | | delay | 采样间隔 |
mpstat输出的各列为:
| user | 用户时间 | | nice | nice时间 | | system | 系统时间 | | iowait | I/O时间 | | irq | irq时间 | | softirq | softirq时间 | | idle | 空闲时间 |
sar可以收集系统信息:
sar [options] [delay [count]]
| -c | 报告每秒创建的进程数 | | -I {irq SUM ALL XALL } | 中断速度 | | -P {cpu ALL} | 指定CPU | | -q | 运行队列长度 | | -u | CPU使用情况 | | -w | 上下文切换数 | | -o file | 输出到文件 | | -f file | 从文件读取选项 | | delay | 采用间隔 | | count | 样本数 |
sar统计的CPU数据有:
| user | 用户时间 | | nice | nice时间 | | system | 系统时间 | | iowait | I/O时间 | | idle | 空闲时间 | | runq-sz | 运行队列长度 | | plist-sz | 进程数 | | ldavg-1 | 1分钟负载 | | ldavg-5 | 5分钟负载 | | ldavg-15 | 15分钟负载 | | proc/s | 每秒新建线程数(等于vmstat中的forks) | | cswch | 每秒上下文切换 | | intr/s | 每秒中断数 |
oprofile也可以收集系统信息:
opcontrol [--start] [--stop] [--dump]
opcontrol [--list-events] [-event=:name:count:unitmask:kernel:user:]
opreport [-r] [-t]
| -s –start | 开始分析 | | -d –dump | 导出文件 | | –stop | 停止分析 | | -l –list-events | 列出可以采样的事件 | | -event=:name:count:unitmask:kernel:user: | | | –vmlinux=kernel | 内核 |
opreport选项:
| -r –reverse-sort | 逆序显示 | | -t –threshold | |
3 性能工具:系统内存
Linux将内存划分为“页”,这是内存管理的单位。下面的程序可以输出系统中一个页有多少字节:
#include <unistd.h>
int main(int argc, char *argv[]) {
printf("System page size: %d\n", getpagesize());
}
IA32架构下,页通常是4KB。为了提高性能,Linux可以使用HugePage来管理单元。HugePage通常是2MB。
为了提供更多的虚拟内存,当物理内存不足时,Linux将部分内存保存在磁盘上,这部分空间叫做交换分区(swap space)。Linux也会把文件系统中近期访问过的文件缓存在内存中,这部分叫做缓存(cache)。此外,为每个已经打开的文件,Linux会分配读写缓冲区,避免每次写入都执行磁盘IO。
vmstat命令可以提供内存统计信息:
vmstat [-a] [-s] [-m]
| -a | 显示活跃内存和非活跃内存。非活跃内存是已分配未使用的内存。 | | -s | 打印vm表 | | -m | 输出分片信息,同/proc/slabinfo |
vmstat输出的各列如下:
| swpd | 交换到磁盘的内存总量 | | free | 未使用的内存 | | buff | 缓冲区大小,单位KB | | cache | 缓存大小,单位KB | | active | 活跃内存 | | inactive | 非活跃内存 | | si | 磁盘到内存的交换速率,单位KB/s | | so | 内存到磁盘的交换速率,单位KB/s | | pages paged in | 从磁盘读入缓冲区的内存,单位页 | | pages paged out | 从缓冲区写入磁盘的内存,单位页 | | used swap | 已使用的交换分区 | | free swap | 空闲交换分区 | | total swap | 交换分区总量 |
top命令也可以查看内存情况。top输出的各列如下:
| %MEM | 进程内存占比 | | VIRT | 进程虚拟内存总量,含已分配未使用的内存 | | SWAP | 进程使用的交换分区总量,单位KB | | RES | 进程使用的物理内存 | | CODE | 进程可执行代码使用的内存 | | DATA | 进程保存数据和堆栈使用的内存 | | SHR | 和其他进程共享的内存 | | nDRT | 脏页面数量 | | Mem: total, used, free | 内存总量、使用量、空闲量 | | swap: total, used, free | 交换分区总量、使用量、空闲量 | | buffers | 缓冲区大小 |
procinfo提供的内存信息如下:
| Total | 物理内存大小 | | Used | 已使用的物理内存 | | Free | 未使用的物理内存 | | Shared | 不再使用 | | Buffers | 缓冲区 | | Page in | 从磁盘读入的块数量 | | Page out | 向磁盘写入的块数量 | | Swap in | 从交换分区读入的页数量 | | Swap out | 向交换分区写入的页数量 |
free命令也可以提供内存情况:
free [-l] [-t] [-s delay] [-c count]
| -s delay | 采样间隔 | | -c count | 采样次数 | | -l | 显示高端内存和低端内存 | | -t | 显示物理内存和交换分区之和 |
free输出的内容如下:
| Total | 物理内存和交换空间总和 | | Used | 已使用的物理内存和交换空间 | | Free | 未使用的物理内存和交换空间 | | Shared | 已废弃 | | Buffers | 缓冲区 | | Cached | 缓存 | | -/+ buffers/cache | 是否统计缓冲区和缓存 | | Low | 低端内存,即可由内核直接访问的内存 | | High | 高端内存 |
slabtop可以显示内存分配情况:
slabtop [--delay n -sort={a|b|c|l|v|n|o|p|s|u}
| –delay | 采样间隔 | | 排序 | | | a | 活跃对象 | | b | 全部对象 | | c | 缓存内存总量 | | l | 缓存分片数 | | v | 缓存活跃分片数 | | n | 缓存名 | | o | 对象数量 | | p | 每个分片使用的页数量 | | s | 对象大小 |
sar可以输出内存信息:
sar [-B] [-r] [-R]
| -B | 缺页数量 | | -W | 交换页数量 | | -r | 内存信息 |
sar输出的内容有:
| pgpgin/s | 每秒从磁盘读入的数据,单位KB | | pgpgout/s | 每秒向磁盘写入的数据,单位KB | | fault/s | 每秒缺页,可能不用访问磁盘 | | majflt/s | 每秒缺页(major fault),需要访问磁盘 | | pswpin/s | 每秒读入内存的交换分区页数 | | pswpin/s | 每秒写入交换分区的页数 | | kbmemfree | 空闲物理内存,单位KB | | kbmemused | 已使用物理内存,单位KB | | %memused | 已使用物理内存占比 | | kbbuffers | 缓冲区 | | kbcached | 缓存 | | kbswpfree | 空闲交换分区 | | kbswpused | 已使用交换分区 | | %swpused | 已使用交换分区占比 | | kbswpcad | 在物理内存和交换分区都存在的内存 | | frmpg/s | 系统释放页的速度。如果是负数,说明系统正在分配页。 | | bufpg/s | 系统将新页作为缓冲区的速度。如果是负数,说明缓冲区正在减少。 |
查看文件文件/proc/meminfo也可以得到内存信息:
| MemTotal | 物理内存 | | MemFree | 物理内存空闲 | | Buffers | 缓冲区 | | Cached | 缓存 | | SwapCached | 在交换分区和物理内存中都存在的内存 | | Active | 活跃内存 | | Inactive | 非活跃内存 | | HighTotal | 高端内存 | | HighFree | 空闲高端内存 | | LowTotal | 低端内存 | | LowFree | 空闲低端内存 | | SwapTotal | 交换内存 | | SwapFree | 空闲交换内存 | | Dirty | 待刷新到磁盘的内存 | | Writeback | 刷新到磁盘的内存 | | Mapped | mmap映射的内存 | | Slab | 内核分片内存 | | Committed_AS | | | PageTables | | | VmallocTotal | vmalloc可用内核内存 | | VmallocUsed | vmalloc已用内核内存 | | VmallocChunk | vmalloc最大可用连续内存 | | HugePages_Total | HugePage总大小 | | HugePages_Free | 空闲HugePage |
4 性能工具:特定进程CPU
time命令(/usr/bin/time,不是bash内置的time函数)可以输出
time [-v] application
time命令的输出如下
| User time | 应用程序消耗的用户时间 | | System time | 程序在Linux内核中消耗的时间 | | Elapsed(wall-clock) | 应用程序执行的时间 | | Percent of CPU this job got | 进程消耗的CPU占比 | | Major(requiring I/O) page faults | 主缺页错误数 | | Minor(reclaiming a frame) page faults | 次缺页错误数 | | Swaps | 交换到磁盘的次数 | | Voluntary context switches | 进程主动让出CPU(比如休眠)的次数 | | Involuntary context switchs | 进程被动让出CPU(如中断)的次数 | | Page size | 页大小 | | Exit status | 退出码 |
strace是一个观察系统调用执行情况的工具:
strace [-c] [-p pid] [-o file] [command [arg ...]]
| -c | 打印统计信息 | | -p pid | 跟踪进程 | | -o file | 输出到文件 |
strace输出各类内容为:
| % time | 系统调用耗时占全部系统调用耗时总和的比例 | | seconds | 系统调用消耗的秒数 | | usecs/call | 平均每场调用消耗的微秒数 | | calls | 调用次数 | | errors | 调用失败次数 |
ltrace和strace类似,区别在于ltrace跟踪程序对库的调用:
ltrace [-c] [-p pid] [-o filename] [-S] command
| -c | 显示汇总信息 | | -s | 跟踪库调用和系统调用 | | -p pid | 跟踪指定进程 | | -o file | 输出到文件 |
ltrace输出各类内容和strace类似:
| % time | 库调用耗时占全部库调用耗时总和的比例 | | seconds | 库调用消耗的秒数 | | usecs/call | 平均每场调用消耗的微秒数 | | calls | 调用次数 | | function | 函数名 |
ps也是一个很好的工具:
ps [-o etime,time,pcpu,command] [-u user] [-U user] [PID]
| etime | 从程序开始执行的总时间 | | time | CPU时间,即系统时间和用户时间之和 | | pcpu | 消耗CPU占比 |
在执行动态链接程序时,首先运行的是Linux加载器ld.so。要观察程序启动时ld消耗的时间,可以用:
env LD_DEBUG=statistics,help LD_DEUBG_OUTPUT=filename <command>
gprof [-p -flat-profile -q --graph --brief -A -annotated-source] app
5 性能工具:特定进程内存
ps [-o vsz,rss,tsiz,dsiz,majflt,minflt,pmem,command] <pid>
| vsz | 进程使用的虚拟内存 | | rss | 进程使用的物理内存 | | tsiz | 程序代码的虚拟大小 | | dsiz | 数据的虚拟大小 | | majflt | 主缺页故障,需要从执行磁盘IO | | minflt | 次缺页故障,不需要执行磁盘IO | | pmep | 进程内存占比 | | command | 命令 |
/proc/<PID>/status
| VmSize | 进程的虚拟内存大小 | | VmLck | 被锁定的虚拟内存大小,被锁定的虚拟内存不能交换到磁盘 | | VmRSS | 物理内存大小,同ps中的rss | | VmData | 数据大小,不含堆栈 | | VmStk | 堆栈大小 | | VmExec | 可执行虚拟内存大小,不含库 | | VmLib | 库大小 |
/proc/<PID>/maps
| Address | 地址 | | Permissions | 权限。r:读,w:写,x:执行,s:共享,p:私有 | | Offset | 偏移量 | | Device | 设备 | | Inode | 文件节点 | | Pathname | 文件路径 |
valgrind --skin-cachegrind application
cg_annotation --pid [--auto=yes|no]
kcachegrind可以检测高速缓存。
calltree application
kcachegrind cachegrind.out.pid
ipcs [-t] [-c] [-l] [-u] [-p]
6 性能工具:磁盘I/O
Linux上IDE驱动的磁盘多被命名为hda、hdb、hdc等,SCSI驱动的硬盘被命名为sda、sdb、sdc等。分区名是在磁盘名后面添加分区号,比如首个IDE磁盘的第二个分区是/dev/hda2。
vmstat可以用来观察磁盘I/O情况:
vmstat [-D] [-d] [-p partition] [interval [count]]
| -D | 显示I/O系统统计信息,显示总量 | | -d | 显示从系统启动后的总量 | | -p partition | 显示分区 | | interval | 采样间隔 | | count | 采样次数 |
vmstat输出的I/O统计信息有:
| bo | 写入磁盘的块数,一个块通常KB | | bi | 读取磁盘的块数 | | wa | 等待I/O消耗的CPU时间 | | reads: total | 读请求总数 | | reads: merged | 合并的读请求数 | | reads: sectors | 读取的扇区数 | | reads: ms | 读取磁盘消耗的时间 | | writes: total | 写请求总数 | | writes: merged | 合并的写请求数量 | | writes: sectors | 写扇区数量 | | IO: cur | 当前处理的I/O数 | | IO: s | 等待I/O完成消耗的时间 |
iostat [-d] [-k] [-x] [device] [interval] [count]
| -d | 值显示磁盘I/O统计信息 | | -k | 以KB为单位,默认以块为单位 | | -x | 显示扩展性能I/O统计信息 | | device | 指定设备 | | interval | 采样间隔 | | count | 采样数 |
iostat输出的内容如下:
| tps | 每秒传输次数 | | Blk_read/s | 每秒读取的块数 | | Blk_wrtn/s | 每秒写入的块数 | | Blk_read | 读取的块总量 | | Blk_wrtn | 写入的块总量 | | rrqm/s | 合并的读请求数 | | wrqm/s | 合并的写请求数 | | r/s | 每秒提交读请求数 | | w/s | 每秒提交写请求数 | | rsec/s | 每秒读取扇区数 | | wsec/s | 每秒写入扇区数 | | rkB/s | 每秒读取的KB数 | | wkB/s | 每秒写入的KB数 | | avgrq-sz | 请求平均大小,单位是扇区 | | avgqu-sz | 请求队列平均大小 | | await | 完成一个请求的平均时间,即在队列中的等待时间和磁盘操作时间之和 | | svctm | 执行操作的平均时间,即await减去队列中的等待时间 |
sar -d [interval [count]]
| tps | 每秒传输数 | | rd_sec/s | 每秒读取数量 | | wr_sec/s | 每秒写入数量 |
lsof [-r delay] [+D directory] [+d directory] [file]
| -r delay | 采样间隔 | | +D directory | 递归监视指定目录,报告使用目录中文件的进程 | | +d directory | 监视指定目录,报告使用目录中文件的进程 |
lsof输出的内容有:
| COMMAND | 进程命令 | | PID | 进程编号 | | USER | 用户 | | FD | txt表示可执行文件,mem表示内存映射文件 | | TYPE | 文件类型,REG表示普通文件 | | DEVICE | 设备,可以由ls -al /dev得到 | | SIZE | 文件大小 | | NODE | 索引节点 |
7 性能工具:网络
当数据帧经过内核时,Linux会统计如下信息:
-发送/接收。正常收发的数据。 -错误。可能是电缆损坏或双工不匹配。 -丢弃。由于内存或缓冲区不足丢弃的数据。 -溢出。由于内核或网卡收到太多的帧,被网络丢弃的数据。 -帧。由于物理层面原因被丢弃的帧,如CRC校验错误。 -多播。 -压缩。如PPP(点对点)协议或SLIP(串行线路网际)协议会压缩帧数据。
在Linux下,以太网设备被命名为ethN,如eth0就是第一个以太网设备。PPP设备以pppN命名。
mii-tool可以检测以太网设备信息:
mii-tool [-v] [device]
ethtool [device]
ifconfig [device]
| RX packets | 设备已经接收的数据包数 | | TX packets | 设备已经发送的数据包数 | | errors | 发送或接收时的错误数 | | dropped | 发送或接收时丢弃的包数 | | overruns | 发生缓冲区不足的次数 | | frame | 底层帧错误数 | | carrier | 由于链路(如电缆)故障丢失的数据包数 |
ip -s [-s] link
| bytes | 发送或接收的字节数 | | packets | 发送或接收的数据包数 | | errors | 发送或接收时发生的错误数 | | dropped | 由于网卡缺少资源,导致没有发送或接收的数据包数 | | overruns | 发生缓冲区不足的次数 | | mcast | 接收的多播数据包的数量 | | carrier | 由于链路故障而丢弃的数据包数 | | collsns | 传送时设备发送冲突的次数,当多个设备试图同时使用网络时会发送冲突 |
sar [-n DEV | EDEV | SOCK | FULL] [DEVICE] [interval] [count]
| -n DEV | 显示每个设备发送和接收的字节数 | | -n EDEV | 显示每个设备发送和接收的错误信息 | | -n SOCK | 显示使用套接字(TCP、UDP、RAW)的总数信息 | | -n FULL | 显示所有网络统计信息 | | interval | 采样间隔 | | count | 采样总数 |
sar输出如下:
| rxpck/s | 数据包接收速率 | | txpck/s | 数据包发送速率 | | rxbyt/s | 字节发送速率 | | txbyt/s | 字节接收速率 | | rxcmp/s | 压缩包接收速率 | | txcmp/s | 压缩包发送速率 | | rxmcst/s | 多播包发送速率 | | rxerr/s | 接收错误率 | | txerr/s | 发送错误率 | | coll/s | 发送时以太网冲突率 | | rxdrop/s | 由于linux内核缓冲区不足导致的接收帧丢失率 | | txdrop/s | 由于linux内核缓冲区不足导致的发送帧丢失率 | | rxfram/s | 帧对齐错误导致的接收帧丢失率 | | rxfifo/s | 由于FIFO错误导致的接收帧丢失率 | | txfifo/s | 由于FIFO错误而导致的发送帧丢失率 | | totsck | 正在使用的套接字数量 | | tcpsck | TCP套接字数量 | | udpsck | UDP套接字数量 | | rawsck | RAW套接字数量 | | ip-frag | IP分片总数 |
iptraf [-d interface] [-s interface] [-t <minutes>]
| -d interface | 显示详细的接口信息 | | -s interface | 显示接口上哪些端口在使用 | | -t <minutes> | 运行时间 |
netstat [-p] [-c] [-interfaces=<name>] [-s] [-t] [-u] [-w]
| -p | 显示套接字对应的PID | | -c | 每秒刷新 | | –interfaces=<name> | 显示指定接口 | | –statistics, -s | 显示IP、UDP、ICMP、TCP统计信息 | | –tcp, -t | 显示TCP套接字信息 | | –udp, -u | 显示UDP套接字信息 | | –raw, -w | 显示RAW套接字信息 |
8 实用工具:性能工具助手
while true; do sleep 1; /sbin/ifconfig eth0 | grep 'RX packets'; done;
script命令会启动一个新shell,记录shell中的所有输入和输出。
script [-a] [-t] [file]
| -a | 追加写入文件 | | -t | 计时 | | file | 输出文件名,默认是typescript |
在使用时,最好先设置为哑终端:
export TERM=dumb
script ps_output
watch每秒运行一次命令,并将命令输出的变化突出显示:
watch [-d [=cumulative]] [-n sec] <command>
| -d[=cumulative] | 突出显示变化 | | -n sec | 采样间隔 |
watch -n 10 "ps -o minflt,cmd"
ldd <binary>
objdump -T <binary>