本文只是在阅读《性能之巅》的过程中,对一些觉得有用的地方进行的总结和摘录,并附加一些方便理解的材料,完整内容还请阅读Gregg的大作
概念和方法
性能分析领域一词的全栈代表了整个操作系统的软硬件在内的所有事物
软件生命周期和性能规划
软件项目生命周期从构思到开发到生产部署的理想步骤包括如下部分,**性能问题随着开发的推进难度会越来越大。**云计算环境能够让用户跳过前5个步骤(蓝绿部署)
1.设置性能目标和建立性能模型
2.基于软件或硬件原型进行性能特征归纳
3.对开发代码进行性能分析(软件整合之前)
4.执行软件非回归性测试(软件发布前或发布后)
5.针对软件发布版本的基准测试
6.目标环境中的概念验证(Proof-of-concept)测试
7.生产环境部署的配置优化
8.监控生产环境中运行的软件
9.特定问题的性能分析
性能分析视角
性能分析可以从不同角度触发,典型的两种性能分析的视角:负载分析(workload analysis)和资源分析(resource analysis)。开发人员工程采用负载视角,运维人员则从资源视角出发
性能分析出发点
性能问题有其主观性(需要客观明确的标准)和复杂性(问题是面而非单个的点)。
-
应用程序和内核通过在执行逻辑中硬编码计数器,提供关于活动和状态的数据(操作数,字节数,使用率,延时,错误率等),根据这些数据可以进一步计算统计指标(平均值,比率,百分比等)
-
除了被动获取技术器数据外,还可以通过profile工具按需采样(火焰图)
-
除了分析数据外,还可以对事件记录进行分析,捕获事件数据并保存和分析(系统调用
strace
,网络数据包tcpdump
)
- 静态检测指在源码硬编码的埋点,linux内核中有数百个检测点(tracepoint),内核空间和用户空间分别有对应的技术
- 动态检测指在程序运行后,在内存中插入检测指令(例如bpf)
-
观测之外的手段,即通过实验的方式进行基准测试,分为宏观(模拟真实负载)和微观基准测试
-
好的工具能够提升关测和分析的效率,linux的60秒快速分析涉及到如下工具
工具 检查 uptime
平均负载识别系统负载的变化 `dmesg -T tail` vmstat -SM 1
系统级统计,运行队列长度,交换,cpu总体状况 mpstat -P ALL 1
CPU平衡情况 pidstat 1
单个进程的cpu使用 iostat -sxz 1
磁盘io统计,iops,吞吐量,平均等待事件,utils等 free -m
内存使用,cache和buffer sar -n DEV 1
网络设备io,数据包和吞吐量 sar -n TCP,ETCP 1
tcp统计,连接和重传 top
概览
性能术语
关于性能的一些术语
- IOPS:每秒发生的输入/输出操作的次数
- 吞吐量:评价工作执行的速率,尤其是在数据传输方面,这个术语用于描述数据传输速度(字节/秒或比特/秒)。在某些情况下(如数据库),吞吐量指的是操作的速度(每秒操作数或每秒业务数)
- 响应时间:一次操作完成的时间。包括用于等待和服务的时间,也包括用来返回结果的时间。
- 延时:延时是描述操作里用来等待服务的时间。在某些情况下,它可以指的是整个操作时间,等同于响应时间。
- 使用率:对于服务所请求的资源,使用率描述在所给定的时间区间内资源的繁忙程度。对于存储资源来说,使用率指的就是所消耗的存储容量
- 饱和度:指的是某一资源无法满足服务的排队工作量。
- 瓶颈:在系统性能里,瓶颈指的是限制系统性能的那个资源。
- 工作负载:系统的输入或者是对系统所施加的负载叫做工作负载。对于数据库来说,工作负载就是客户端发出的数据库请求和命令。
- 缓存:用于复制或者缓冲一定量数据的高速存储区域,避免对较慢的存储层级的直接访问从而提高性能
对其中一些概念的抽象能够更好的体会量级的差异,延时和吞吐量尤其需要注意
项目就是在性能,响应速度和成本之间进行权衡的过程,性能调优在应用程序本身进行效果是最显著的
性能分析方法
性能分析的方法很多,完整理论部分参考《性能之巅》的具体章节,感觉比较好理解的方法有
- USE方法,遍历资源(硬件和软件资源),根据资源在utilization、saturation、errors三个方面寻找测量指标,指标的异常往往体现了性能问题
- 工作负载特征归纳,关注负载导致的问题(系统的输入),例如负载的产生,调用逻辑,负载特征(iops,吞吐量,读写,变动方向)
- 性能箴言,包括一些典型的调优手段
- 不要做,消除不必要的工作
- 做但不要再做,利用缓存
- 做少一点,将刷新,轮询和更新的频率降低
- 回头再做,回写缓存
- 在夜深人静的时候做,错开时间
- 同时做,多线程
- 做的更便宜,提升硬件性能
操作系统
系统内核
linux内核管理着CPU 调度、内存、文件系统、网络协议,以及系统设备。
相比系统调用,系统库提供的编程接口通常更为丰富和简单。应用程序本身可以直接进行系统调用(操作系统允许)
频繁IO的工作负载(网络服务器)主要在内核态运行,计算密集型的工作负载通常在用户态运行。因此,查看cpu的不同态的事件可以判断工作负载的特征
内核的执行主要是按需的,例如,当用户级别的程序发起一次系统调用,或者设备发送一个中断时。一些内核线程会异步地执行一些系统维护的工作,其中可能包括内核时钟程序和内存管理任务
经典的UNIX 内核的一个核心组件是clock()例程(存在性能问题),现代内核已经把许多功能移出了clock 例程,放到了按需中断中,这是为了努力创造无tick内核
内核是唯一运行在内核态的程序(允许设备访问和特权指令)。用户程序(进程)运行在用户态,对于内核特权操作(例如I/O)的请求是通过系统调用传递的。执行系统操作,执行会做上下文切换从用户态到内核态,然后用更高的特权级别执行
用户态和内核态程序有自身的上下文,上下文状态切换有性能损耗(发生在状态切换和进程切换)。有以下的优化方式避免模式切换
- 用户态系统调用,内核导出虚拟动态共享对象到进程地址空间
- 内存映射
- 内核旁路(bypass),例如
DPDK
- 内核态应用程序(用户态docker)
系统调用是请求内核执行特权指令的例程,在内核中实现(因此要保证简单),复杂的接口可以在用户空间通过系统库实现(例如glibc)
中断分为异步中断(硬件产生)和同步中断(软件产生)
对于Linux 而言,设备驱动分为两半,上半部用于快速处理中断(在中断禁止模式,不能耗时太长),到下半部的调度工作在之后处理(作为tasklet或工作队列)
进程是用以执行用户级别程序的环境。它包括内存地址空间、文件描述符、线程栈和寄存器。一个进程中包含有一个或多个线程,线程是一个可执行的上下文,包括栈、寄存器,以及程序计数器
调度器,进程在处理器上和CPU 间的调度是由调度器完成的,这是操作系统内核的关键组件。基本的意图是将CPU 时间划分给活跃的进程和线程,而且维护一套优先级的机制
调度器会跟踪所有处于ready-to-run 状态的线程,传统意义上每一个优先级队列都称为运行队列,每个cpu都有一个队列。多数的内核线程运行的优先级要比用户级别的优先级高。此处的优先级仅仅只时间片的长度,和运行的优先级没有关系(抢占式调度)。内核中存在动态优化的逻辑执行类似以下操作
调度器能够识别CPU 密集型的进程并降低它们的优先级,可以让I/O 密集型工作负载(需要低延时响应)更快地运行
文件系统是作为文件和目录的数据组织。有一个基于文件的接口用于访问,该接口通常基于POSIX 标准。虚拟文件系统(virtual file system,VFS)是一个对文件系统类型做抽象的内核界面
基于存储设备的文件系统,从用户级软件到存储设备的路径被称为I/O 栈
由于磁盘I/O 的延时较长,软件栈中的很多层级通过缓存读取和缓存写入来试图避免,常见的缓存包括
缓存 | 实例 |
---|---|
客户端缓存 | 浏览器缓存 |
应用程序缓存 | - |
webserver缓存 | apache缓存 |
缓存服务器 | redis |
数据库缓存 | mysql缓冲区告诉缓存 |
目录缓存 | dcache |
文件元数据缓存 | inode缓存 |
操作系统缓冲区高速缓存 | 缓冲区高速缓存 |
文件系统主缓存 | 换页缓存 |
文件系统次缓存 | ZFS L2ARC |
设备缓存 | ZFS vdev |
快缓存 | 缓冲区高速缓存 |
磁盘控制器缓存 | RAID卡缓存 |
存储阵列缓存 | - |
磁盘内存缓存 | - |
内核提供一套内置的网络协议栈(TCP/IP 栈),能够让系统用网络进行通信。网络协议通常不会变,但是新的TCP 选项和新的TCP 阻塞控制算法需要内核支持。另一个可能的变化是对于不同的网络接口卡的支持,需要内核有新设备的驱动
设备驱动是用于设备管理和设备I/O 的内核软件,常常由开发硬件设备的厂商提供
设备驱动提供给设备的接口有字符接口也有块接口。字符设备,也称为原始设备,提供无缓冲的设备顺序访问,访问可以是任意I/O 尺寸的,也可以小到单一字符,取决于设备本身
多处理器架构下,NUMA架构允许主存连接到不同的物理CPU
观测工具
Linux 性能调优工具9张图
linux工作负载性能观测工具
工具的维度划分,系统级别和进程级别
观测工具只是通过各种结构和框架整理信息,数据来源包括
例如,/proc
是一个提供内核统计信息的文件系统接口,将内核统计数据用目录树的形式暴露给用户空间,接口就是POSIX文件系统调用。/sys
在2.6 内核引入的,为内核统计提供一个基于目录的结构
/proc进程级别的接口如下
- limits:实际的资源限制
- maps:映射的内存区域
- sched:CPU 调度器的各种统计
- schedstat:CPU 运行时间、延时和时间分片
- smaps:映射内存区域的使用统计
- stat:进程状态和统计,包括总的CPU 和内存的使用情况
- statm:以页为单位的内存使用总结
- status:stat 和statm 的信息,用户可读
- task:每个任务的统计目录
/proc系统级别的接口如下
- cpuinfo:物理处理器信息,包含所有虚拟CPU、型号、时钟频率和缓存大小
- diskstats:对于所有磁盘设备的磁盘I/O 统计
- interrupts:每个CPU 的中断计数器
- loadavg:负载平均值
- meminfo:系统内存使用明细
- net/dev:网络接口统计
- net/tcp:活跃的TCP 套接字信息
- schedstat:系统级别的CPU 调度器统计
- self:关联当前进程ID 路径的符号链接
- slabinfo:内核slab 分配器缓存统计
- stat:内核和系统资源的统计,CPU、磁盘、分页、交换区、进程
- zoneinfo:内存区信息。
sar
也是一个不可忽视的观测工具,以下是可以覆盖的观测范围
这里只列出了常见的观测数据源,详细需要参考原著
之后就是对应用程序,cpu,内存,文件系统,磁盘,网络等进行的具体分析
应用程序
性能调整离工作所执行的地方越近越好:最好在应用程序里
Go 语言高性能编程
分析问题
对于一个应用程序的分析应当考虑以下问题
- 功能:应用程序的角色是什么?是数据库、Web 服务器、负载均衡器、文件服务器,还是对象存储?
- 操作:应用服务器服务哪些请求,或者执行怎么样的操作?数据库服务查询(和命令)、Web 服务器服务HTTP 请求,等等。这些可以用速率来度量,用以估计负载和做容量规划。
- CPU 模式:应用程序是用户级的软件实现还是内核级的软件实现?多数的应用程序是用户级别的,以一个或多个进程的形式执行,但是有些是以内核服务的形式实现的(例如,NFS)。
- 配置:应用程序是怎样配置的,为什么这么配?这些信息能在配置文件里找到或者用管理工具得到。检查所有与性能相关的可调参数有没有改过,包括缓冲区大小、缓存大小、并发(进程或线程),以及其他选项。
- 指标:有没有可用的应用程序指标,如操作率?可能是用自带工具或者第三方工具,通过API 请求,或者通过处理操作日志得到。
- 日志:应用程序创建的操作日志是哪些?能启用什么样的日志?哪些性能指标,包括延时,能从日志中得到?
- 版本:应用程序是最新的版本吗?在最近的版本的发布说明里有没有提及性能的修复和性能的提升?
- Bugs:应用程序有bug 数据库吗?你用的应用程序版本有什么样的“性能”bug?
- 社区:应用程序社区里有分享性能发现的地方吗?
- 书:有与应用程序以及它的性能相关的书吗?
- 专家:谁是这个应用程序公认的性能专家?
分析目标
应用程序性能分析的目标
- 延时:低应用程序响应时间
- 吞吐量:高应用程序操作率或者数据传输率
- 资源使用率:对于给定应用程序工作负载,高效地使用资源
常用的调优手段
提高应用程序性能的常用技术包括:选择I/O 大小、缓存、缓冲区、轮询、并发和并行、非阻塞I/O 和处理器绑定
-
IO大小:IO大小越大,每次传输的数据越多,效率越高。然而随机读写大的IO会造成浪费(IO时间和缓存)
执行I/O 的开销包括初始化缓冲区、系统调用、上下文切换、分配内核元数据、检查进程权限和限制、映射地址到设备、执行内核和驱动代码来执行I/O,以及,在最后释放元数据和缓冲区。
-
缓存:存储通常用缓存来提高写操作的性能,需要注意缓存一致性(避免返回过期数据)
12 张图看懂 CPU 缓存一致性与 MESI 协议
-
缓冲区:写操作同样有写缓冲区,将IO进行合并减少开销,但是会增加写延迟。环形缓冲区(例如网卡驱动缓冲)提供了固定大小的缓冲区
-
轮询:不断检查事件状态,导致CPU开销,两个检查存在延迟。通过poll和epoll接口提供基于事件的触发,避免轮询
-
并发:可以用多进程(multiprocess)或者多线程(multithreaded)实现,需要调度器上下文切换开销为代价。可以在用户态实现调度:协程(goroutine),基于事件的并发(nodejs)。提到并发就要考虑到同步原语的问题,即锁可能会导致性能问题
Golang深入理解GPM模型
-
非阻塞IO:非阻塞I/O 模型是异步地发起I/O,而不阻塞当前的线程,线程可以执行其他的工作
-
处理器绑定:NUMA架构下,线程执行I/O 前后运行在同一CPU 上。提高了应用程序的内存本地性,减少内存I/O
编程语言级别
- 编程语言:不同语言的选择,编译型语言编译优化的考量,解释性语言解释器的分析
- 语言虚拟机:性能分析通常靠的是语言虚拟机提供的工具集(一些虚拟机提供DTrace 探针)和第三方的工具
- GC(垃圾回收):影响内存,CPU损耗和GC导致的延时有,例如,Java虚拟机提供了许多可调参数来设置GC 类型、GC 的线程数、堆尺寸的最大值、目标堆的空闲率
具体的分析方法
包括CPU剖析,系统调用,线程状态,IO,工作负载,USE方法,锁分析
线程状态的分析
确定应用程序的线程在哪里花费了时间,线程的9种状态
- 用户:用户态的on-cpu,哪些代码在消耗CPU
- 内核:内核态的on-cpu,哪些代码在消耗CPU
- 可运行:等待轮到上CPU,意味着需要申请资源,考虑是否存在资源控制(cgroup)
- 匿名换页:可运行,但是因等待匿名换页而受阻,内存不足可能换页,考虑资源控制
- 磁盘IO:等待块设备IO
- 网络IO:等待网络折欸IO,套接字读写
- 睡眠:等待包括网络、块设备和数据/文本页换入在内的I/O。
- 锁:等待获取同步锁(等待其他线程)
- 空闲:等待工作
静态性能调优的问题
- 在运行的应用程序是什么版本?有更新的版本吗?发布说明有提及性能提高吗?
- 应用程序有哪些已知的性能问题?有可供搜索的bug 数据库吗?
- 应用程序是如何配置的?
- 如果配置或调整的与默认值不同,是由于什么原因?(是基于测量和分析,还是猜想?)
- 应用程序用到了对象缓存吗?缓存的大小是怎样的?
- 应用程序是并发运行的吗?这是如何配置的(例如,线程池大小)?
- 应用程序运行在特定模式下吗?应用程序用到了哪些系统库?它们的版本是什么?
- 应用程序用的是怎样的内存分配器?
- 应用程序配置用大页面做堆吗?
- 应用程序是编译的吗?编译器的版本是什么?编译器的选项和优化是哪些?是64 位的吗?
- 应用程序遇到错误了吗?错误之后会运行在降级模式吗?
- 有没有系统设置的限制,以及对CPU、内存、文件系统、磁盘和网络使用的资源控制?(在云计算中很普遍)
CPU
概念和架构
cpu运行队列,花在等待CPU 运行上的时间又被称为运行队列延时或者分发器队列延时(调度器延时)。对于多处理器系统,内核通常为每个CPU 提供了一个运行队列,并尽量使得线程每次都被放到同一队列之中(热缓存)。在NUMA系统中,这会提高内存本地性,从而提高系统性能
-
时钟频率,更快的时钟频率并不一定会提高性能,取决于快速CPU 周期指令。指令流水线能够提升cpu的吞吐量。
-
每指令周期(CPI):CPI 较高代表CPU 经常陷入停滞,通常都是在访问内存。而较低的CPI 则代表CPU 基本没有停滞,指令吞吐量较高
-
使用率:CPU 使用率通过测量一段时间内CPU 实例忙于执行工作的时间比例获得。CPU 使用率的测量包括了所有符合条件活动的时钟周期,包括内存停滞周期(等待IO的时间)。CPU 使用率通常被分成内核时间和用户时间两个指标
-
饱和度:100%使用率的CPU 被称为是饱和的,线程在这种情况下会碰上调度器延时,也有可能被cgroup限制
-
多进程和多线程的比较
-
字长:表示地址空间大小和数据通路宽度,增加吞吐量,额外的内存开销
-
缓存:不同级别缓存访问时延不同
-
系统总线:在处理器数目增长的情况下,会因为共享系统总线资源而出现扩展性问题。现在多处理器使用NUMA架构和cpu互联技术
-
调度器,具备的功能有
- 分时:可运行线程之间的多任务,优先执行最高优先级任务
- 抢占:一旦有高优先级线程变为可运行状态,调度器能够抢占当前运行的线程,这样较高优先级的线程可以马上开始运行
- 负载均衡:把可运行的线程移到空闲或者较不繁忙的CPU 队列中
-
调度类:管理了可运行线程的行为,优先级,CPU 时间是否分片,以及这些时间片的长度(又称为时间量子)。用户线程的优先级受一个用户定义的一个nice 值影响。可以对不重要的工作设置这个值以降低其优先级
linux下的调度类有
- RT:为实时类负载提供固定的高优先级。内核支持用户和内核级别的抢占,允许RT任务以短延时分发。优先级范围为0~99
- O(1)调度器:在Linux 2.6 作为默认用户进程分时调度器引入
- CFS:Linux 2.6.23 引入了完全公平调度作为默认用户进程分时调度器
用户级进程可以通过调用sched_setscheduler()设置调度器策略以调整调度类的行为。RT 类支持
SCHED_RR
和SCHED_FIFO
策略,而CFS 类支持SCHED_NORMAL
和SCHED_BATCH
常用工具
- uptime:检查负载平均数以确认CPU 负载是随时间上升还是下降。负载平均数超过了CPU 数量通常代表CPU 饱和
- vmstat:每秒运行vmstat,然后检查空闲列,看看还有多少余量。少于10%可能是一个问题
- mpstat:检查单个热点(繁忙)CPU,挑出一个可能的线程扩展性问题
- top/prstat:看看哪个进程和用户是CPU 消耗大户
- pidstat/prstat:把CPU 消耗大户分解成用户和系统时间
- perf/dtrace/stap/oprofile:从用户时间或者内核时间的角度剖析CPU 使用的堆栈跟踪,以了解为什么使用这么多CPU。●
- perf/cpustat:测量CPI
具体的分析方法
USE方法
检车美俄cpu的
- 使用率:CPU 繁忙的时间(未在空闲线程中)。
- 饱和度:可运行线程排队等待CPU 的程度。
- 错误:CPU 错误,包括可改正错误
负载特征分析
- 平均负载(使用率+饱和度)
- 用户时间与系统时间之比
- 系统调用频率
- 自愿上下文切换频率
- 中断频率
- 整个系统范围内的CPU 使用率是多少?每个CPU 呢?
- CPU 负载的并发程度如何?是单线程吗?有多少线程?
- 哪个应用程序或者用户在使用CPU?用了多少?
- 哪个内核线程在使用CPU?用了多少?
- 中断的CPU 用量是多少?
- CPU 互联的使用率是多少?
- 为什么CPU 被使用(用户和内核级别调用路径)?
- 遇到了什么类型的停滞周期?
静态性能调优
- 有多少CPU 可用?是核吗?还是硬件线程?
- CPU 的架构是单处理器还是多处理器?
- CPU 缓存的大小是多少?是共享的吗?
- CPU 时钟频率是多少?是动态的(例如Intel 睿频加速和SpeedStep)吗?这些动态特性在BIOS 启用了吗?
- BIOS 里启用或者禁用了其他什么CPU 相关的特性?
- 这款型号的处理器有什么性能问题(bug)?出现在处理器勘误表上了吗?
- 这个BIOS 固件版本有什么性能问题(bug)吗?
- 有软件的CPU 使用限制(资源控制)吗?是什么?
常用性能指标
- 平均负载:表示了对CPU 资源的需求,通过汇总正在运行的线程数(使用率)和正在排队等待运行的线程数(饱和度)计算得出。Linux 目前把在不可中断状态执行磁盘I/O 的任务也计入了平均负载。这意味着平均负载再也不能单用来表示CPU 余量或者饱和度
- vmstat指标
- r:运行队列长度——可运行线程的总数
- us:用户态时间。
- sy:系统态时间(内核)。
- id:空闲
- wa:等待I/O,即线程被阻塞等待磁盘I/O 时的CPU 空闲时间
- st:偷取(未在输出里显示),CPU 在虚拟化的环境下在其他租户上的开销
- mpstat指标
- CPU:逻辑CPU ID,或者all 表示总结信息
- %usr:用户态时间
- %nice:以nice 优先级运行的进程用户态时间
- %sys:系统态时间(内核)
- %iowait:I/O 等待
- %irq:硬件中断CPU 用量
- %soft:软件中断CPU 用量
- %steal:耗费在服务其他租户的时间
- %guest:花在访客虚拟机的时间
- %idle:空闲。
内存
概念和架构
-
虚拟内存:抽象概念,它向每个进程和内核提供巨大的、线性的并且私有的地址空间。支持多任务(虚拟地址空间被设计成分离的),并且可以超额订购(使用中的内存可以超出主内存的容量)
-
换页:
- 文件系统换页(好的):读写位于内存中的映射文件页引发,内存不足时可以回收的cache
- 匿名换页(坏的):匿名换页牵涉进程的私有数据:进程堆和栈(匿名是由于它在操作系统中缺乏有名字的地址,例如,没有文件系统路径),要求迁移数据到物理交换设备或者交换文件
-
按需换页:将虚拟内存按需映射到物理内存,把CPU 创建映射的开销推迟到实际需要或访问时,而不是在初次分配这部分内存时
虚拟内存和按需换页的结果是任何虚拟内存页可能处于如下的一个状态:(如果因为系统内存压力而换出页就会到达D 状态。状态B 到C 的转变就是缺页。如果需要磁盘读写,就是严重缺页,否则就是轻微缺页)
- A.未分配
- B.已分配,未映射(未填充并且未缺页)
- C.已分配,已映射到主存(RAM)
- D.已分配,已映射到物理交换空间(磁盘)
云计算环境没有交换区,不鼓励坏的换页
其他相关概念
-
RSS(C)常驻内存集,已经分配的主存
-
虚拟内存(B+C+D),所有已分配的区域
-
WSS 工作集,频繁使用的主存大小
-
进程交换(unix独创):交换是在主存与物理交换设备或者交换文件之间移动整个进程,严重影响性能
-
文件系统缓存,系统启动之后内存的占用增加是正常的,因为操作系统会将可用内存用于文件系统缓存以提高性能。在应用程序需要的时候,内核应该能够很快从文件系统缓存中释放内存
-
饱和度:对内存的需求超过了主存的情况被称作主存饱和。这时操作系统会使用换页、交换或者OOM killer来释放内存
-
内存释放:系统中的可用内存过低时,内核有多种方法释放内存,并添加到页空闲链表中(如果在未被重用前有对任一页的请求,它能被取回并从空闲链表中移除)
- 回收大多是从内核的slab 分配器缓存释放内存。这些缓存包含slab 大小的未使用内存块,以供重用。回收将这些内存交还给系统进行分配
- 内核页面换出守护进程(kswapd)管理利用换页释放内存。当主存中可用的空闲链表低于阈值时,页面换出守护进程会开始页扫描。kswapd 先扫描非活动列表,然后按需扫描活动列表。术语扫描指遍历列表检查页面:如果页被锁定或者是脏的,它可能不适合释放
-
内存分配:用户态库或者内核程序向程序员提供简单的内存使用接口。分配器可以利用包括线程级别对象缓存在内的技术以提高性能
分配器包括,内核级分配器——slab 分配器和SLUB——以及用户级分配器——glibc,jemalloc和TCMalloc
glibc内存管理那些事儿
常用工具
- 页扫描:寻找连续的页扫描(超过10 秒),它是内存压力的预兆。Linux 中,可以使用sar -B 并检查pgscan 列。
- 压力滞留信息(PSI):查看
/proc/pressure/memeory
检查内存压力(饱和度) - 换页:换页是系统内存低的进一步征兆。Linux 中,可以使用vmstat (8)并检查si和so 列(这里,交换指匿名换页)。
- vmstat:每秒运行vmstat 检查free 列的可用内存
- OOM 终结者:这些事件可以在系统日志/var/log/messages,或者从dmesg(1)中找到
- 交换:配置交换的情况下,用vmstat -S 并检查si 和so
- top/prstat:查看哪些进程和用户是(常驻)物理内存和虚拟内存的最大使用者
- perf/bpftrace:通过栈追踪内存分配
具体分析方法
描述使用情况
- 系统范围的物理和虚拟内存使用率
- 饱和度:换页、交换、OOM 终结者
- 内核和文件系统缓存使用情况
- 每个进程的物理和虚拟内存使用情况
- 是否存在内存资源控制
- 内核内存用于何处?每个slab 呢?
- 文件系统缓存(或者页缓存)中不活跃与活跃的比例是多少?
- 进程内存用于何处?
- 进程为何分配内存(调用路径)?
- 内核为何分配内存(调用路径)?
- 哪些进程被持续地页面换出/交换出?
- 哪些进程曾经被页面换出/交换出?
- 进程或者内核是否有内存泄漏?
- NUMA 系统中,内存是否被分配到合适的节点中去?
- CPI 和内存停滞周期频率是多少?
- 内存总线的平衡性?
- 相对于远程内存I/O,执行了多少本地内存I/O?
性能监测
- 使用率
- 饱和度:交换,oom
内存泄露
当应用程序或者内核模块无尽地增长,从空闲链表、文件系统缓存,最终从其他进程消耗内存时,就出现了这个问题
- 内存泄漏:一种类型的软件bug,忘记分配过的内存而没有释放。通过修改软件代码,或应用补丁及进行升级(进而修改代码)能修复。
- 内存增长:软件在正常地消耗内存,远高于系统允许的速率。通过修改软件配置,或者由软件开发人员修改软件内存的消耗方式来进行修复
静态性能调优
- 主存有多少?
- 配置允许应用程序使用多少内存(它们自己的配置)?
- 应用程序使用哪个分配器?
- 主存的速度?是否是可用的最快的类型?
- 系统架构是什么?NUMA、UMA?
- 操作系统支持NUMA 吗?
- 有多少内存总线?
- CPU 缓存的数量和大小是多少?TLB?
- 是否配置和使用了大页面?
- 是否支持和配置了过度提交?
- 还使用了哪些其他的内存可调参数?
- 是否有软件强制的内存限制(资源控制)
可调的内核参数
文件系统
概念和架构
文件系统性能比磁盘性能更为重要。文件系统通过缓存、缓冲以及异步I/O 等手段来缓和磁盘(或者远程系统)的延时对应用程序的影响
-
文件系统接口
-
文件系统缓存。读操作从缓存返回(缓存命中)或者从磁盘返回(缓存未命中)。未命中的操作被存储在缓存中,并填充缓存(热身)。文件系统缓存可能也用来缓冲写操作,使之延时写入
-
文件系统延时,一个文件系统逻辑请求从开始到结束的时间。它包括了消耗在文件系统、内核磁盘I/O 子系统以及等待磁盘设备——物理I/O 的时间
-
随机与顺序IO,一连串的文件系统逻辑I/O,按照每个I/O 的文件偏移量,可以分为随机I/O 与顺序I/O
文件系统一直以来在磁盘上顺序和连续地存放文件数据,以努力减小随机I/O 的数目。当文件系统未能达成这个目标时,文件的摆放变得杂乱无章,顺序的逻辑I/O 被分解成随机的物理I/O,这种情况我们称为碎片化
-
预取,通过检查当前和上一个I/O 的文件偏移量,可以检测出当前是否是顺序读负载,并且做出预测,在应用程序请求前向磁盘发出读命令,以填充文件系统缓存
-
回写缓存,当数据写入主存后,就认为写入已经结束并返回,之后再异步地把数据刷入磁盘。文件系统写入“脏”数据的过程称为刷新(flushing)。影响了数据的可靠性(断电丢失),因此也会也提供一个同步写的选项绕过这个机制,把数据直接写在磁盘上
-
同步写,同步写完成的标志是,所有的数据以及必要的文件系统元数据被完整地写入到永久存储介质(如磁盘设备)中。由于包含了磁盘I/O 的延时,所以肯定比异步写(写回缓存)慢得多
-
裸IO,绕过了整个文件系统,直接发给磁盘地址。其缺点在于难以管理,即不能使用常用文件系统工具执行备份/恢复和监控
-
直接IO,允许应用程序绕过缓存使用文件系统
-
非阻塞IO,如果需要等待,应用程序线程会被阻塞并让出CPU,在等待期间给其他线程执行的机会
-
内存映射文件,通过把文件映射到进程地址空间,并直接存取内存地址的方法来提高文件系统I/O 性能。这样可以避免调用read()和write()存取文件数据时产生的系统调用和上下文切换开销。内存映射通过系统调用mmap()创建,通过munmap()销
Go Mmap 文件内存映射简明教程
-
元数据,数据对应了文件和目录的内容,那元数据则对应了有关它们的信息。元数据可能是通过文件系统接口(POSIX)读出的信息,也可能是文件系统实现磁盘布局所需的信息。前者被称为逻辑元数据,后者被称为物理元数据
-
逻辑和物理IO,与应用程序I/O 相比,磁盘I/O 有时显得无关、间接、放大或者缩小
文件系统的工作不仅仅是在永久存储介质(磁盘)上提供一个基于文件的接口那么简单。它们缓存读、缓冲写,发起额外的I/O 维护磁盘上与物理布局相关的元数据,这些元数据记录了数据存储的位置
- 无关,以下因素可能造成磁盘I/O 与应用程序无关。
- 其他应用程序:磁盘I/O 来源于其他应用程序。
- 其他租户:磁盘I/O 来源于其他租户(可在虚拟化技术的帮助下通过系统工具查看)。
- 其他内核任务:例如内核在重建一个软RAID 卷或者执行异步文件系统校验验证时。
- 间接,以下因素可能造成应用程序I/O 与磁盘I/O 之间没有直接对应关系。
- 文件系统预取:增加额外的I/O,这些I/O 应用程序可能用得到,也可能用不到。
- 文件系统缓冲:通过写回缓存技术推迟和归并写操作,之后再一并刷入磁盘。有些文件系统可能会缓冲数十秒后一起写入,造成偶尔的突发大I/O。
- 缩小,以下因素可能造成磁盘I/O 小于应用程序I/O,甚至完全消失。
- 文件系统缓存:直接从主存返回,而非磁盘。
- 文件系统写抵消:在一次性写回到磁盘之前,同一个地址被修改了多次。
- 压缩:减少了从逻辑I/O 到物理I/O 的数据量。
- 归并:在向磁盘发I/O 前合并连续I/O。
- 内存文件系统:也许永远不需要写入到磁盘的内容(如tmpfs)
- 放大
- 以下因素可能造成磁盘I/O 大于应用程序I/O。
- 文件系统元数据:增加了额外的I/O。
- 文件系统记录尺寸:向上对齐的I/O 大小(增加了字节数),或者被打散的I/O(增加了I/O 数量)。
- 卷管理器奇偶校验:读-改-写的周期会增加额外的I/O。
- 无关,以下因素可能造成磁盘I/O 与应用程序无关。
-
特殊文件系统,有些特殊的文件系统也有着其他用途,比如临时文件(/tmp)、内核设备路径(/dev)和系统统计信息(/proc)
-
文件系统缓存
- 页缓存,缓存了虚拟内存的页面,包括文件系统的页面,提升了文件和目录的性能。页缓存大小是动态的,它会不断增长消耗可用的内存,并在应用程序需要的时候释放
- 目录项缓存(Dcache)记录了从目录项(struct dentry)到VFS inode 的映射关系
- inode 缓存这个缓存的对象是VFS inode(struct inode),每个都描述了文件系统一个对象的属性
-
文件系统类型:ext4,xfs,btrfs等
-
卷和池
具体分析方法
延时分析
操作延时 = 时刻(完成操作)-时刻(发起操作)
负载特征归纳
- 操作频率和操作类型
- 文件I/O 吞吐量
- 文件I/O 大小
- 读写比例
- 同步写比例
- 文件随机和连续访问比例
- 文件系统缓存命中率是多少?未命中率是多少?
- 文件系统缓存有多大?当前使用情况如何?
- 现在还使用了其他什么缓存(目录、inode、高速缓冲区)和它们的使用情况?
- 哪个应用程序或者用户在使用文件系统?
- 哪些文件和目录正在被访问?是创建和删除吗?
- 碰到了什么错误吗?是不是由于一些非法请求,或者文件系统自身的问题?
- 为什么要发起文件系统I/O(用户程序的调用路径)?
- 应用发起的文件系统I/O 中同步的比例占到多少?
- I/O 抵达时间的分布是怎样的?
性能监控
- 操作频率,操作频率是负载的最基本特征
- 操作延时,延时是好是差,取决于负载、环境和延时需求
事件追踪
事件跟踪捕获文件系统每个操作的细节
- 文件系统类型。
- 文件系统挂载点。
- 操作类型:读取、写入、统计、打开、关闭、建目录,等等。
- 操作大小(如果适用):字节数。
- 操作开始时间戳:向文件系统发起操作的时间。
- 操作结束时间戳:文件系统完成操作的时间。
- 操作完成状态:错误。
- 路径名(如果适用)。
- 进程ID。
- 应用程序名。
静态性能调优
- 当前挂载并使用了多少个文件系统?
- 文件系统记录大小?
- 启用了访问时间戳吗?
- 还启用了哪些文件系统选项(压缩、加密等)?
- 文件系统缓存是怎么配置的?最大缓存大小是多少?
- 其他缓存(目录、inode、高速缓冲区)是怎么配置的?
- 有二级缓存吗?用了吗?
- 有多少个存储设备?用了几个?
- 存储设备是怎么配置的?用了RAID 吗?
- 用了哪种文件系统?
- 用了哪个文件系统的版本(或者内核)?
- 有什么需要考虑的文件系统bug/补丁?
- 启用文件系统I/O 的资源控制了吗?
微基准测试的参数
- 操作类型:读、写和其他文件系统操作的频率。
- I/O 大小:从1B 到1MB 甚至更大。
- 文件偏移量模式:随机或者连续。
- 随机访问模式:统一的随机分布或者帕累托分布。
- 写类型:异步或同步(O_SYNC)。
- 工作集大小:文件系统缓存是否放得下。
- 并发:同时执行的I/O 数,或者执行I/O 的线程数。
- 内存映射:文件通过mmap()访问而非read()/write()。
- 缓存状态:文件系统缓存是“冷的”(未填充)还是“热的”。
- 文件系统可调参数:可能包括了压缩、数据消重等。
磁盘
在高负载下,磁盘成为了瓶颈,CPU 持续空闲以等待磁盘I/O 结束
概念和架构
-
缓存磁盘和磁盘控制器
-
磁盘响应时间,也叫磁盘I/O 延时,指的是从I/O 请求到结束的时间。它由服务和等待时间组成
- 服务时间:I/O 得到主动处理(服务)的时间,不包括在队列中等待的时间。
- 等待时间:I/O 在队列中等待服务的时间
响应时间、服务时间和等待时间全部取决于测量所处的位置
- 对内核的角度
- 块io等待时间,块IO创建到插入内核IO队列到离开内核IO队列并发送到设备的事件
- 块io服务事件,发出请求到设备完成中断之间的时间
- 对磁盘的角度
- 磁盘等待时间,话再磁盘队列上的时间
- 磁盘服务时间,进入磁盘队列后IO被主动处理的时间
-
磁盘IO延时的时间尺度
-
缓存,最好的磁盘IO就是没有IO,通过缓存读和缓冲写来避免磁盘I/O 抵达磁盘
-
连续IO和随机IO,读和写,IO大小
-
非磁盘传输命令,将缓存协会磁盘
-
使用率
-
饱和度
-
IO等待,针对单个CPU 的性能指标,表示当CPU 分发队列(在睡眠态)里有线程被阻塞在磁盘I/O 上时消耗的空闲时间。较高的每CPU I/O 等待时间表示磁盘可能是瓶颈所在,导致CPU 等待而空闲
-
磁盘IO和应用程序IO,磁盘I/O 是多个内核组件的终点,包括文件系统和设备驱动。磁盘I/O 与应用程序发出的I/O 在频率和大小上都不匹配。可能原因
- 文件系统放大、缩小和不相关的I/O。
- 由于系统内存短缺造成的换页。
- 设备驱动I/O 大小:I/O 大小的向上取整,或者I/O 的碎片化
具体分析方法
常用工具
- iostat:使用扩展模式寻找繁忙磁盘(超过60%使用率),较高的平均服务时间(超过大概10ms),以及高IOPS(可能)
- iotop:发现哪个进程引发了磁盘I/O。
- bitlatency,直方图检查IO延时分布
- biosnoop,检查单个IO
- bpftrace,发出IO的用户和内核栈
负载特征归纳
- I/O 频率
- I/O 吞吐量
- I/O 大小
- 随机和连续比例
- 读写比
- 系统IOPS 是多少?每个磁盘呢?每个控制器呢?
- 系统吞吐量是多少?每个磁盘呢?每个控制器呢?
- 哪个应用程序或者用户正在使用磁盘?
- 哪个文件系统或者文件正在被访问?
- 碰到什么错误了吗?这些错误是源于非法请求,还是磁盘的问题?
- I/O 在可用磁盘之间均衡吗?
- 每条参与的传输总线上的IOPS 是多少?
- 每条参与的传输总线上的吞吐量是多少?
- 发出了哪些非数据传输磁盘命令?
- 为什么会发起磁盘I/O(内核调用路径)?
- 磁盘I/O 里应用程序同步的调用占到多少?
- I/O 到达时间的分布是什么样的?
性能特征归纳
- 每块盘有多忙(使用率)?
- 每块盘的I/O 饱和度是多少(等待队列)?
- 平均I/O 服务时间是多少?
- 平均I/O 等待时间是多少?
- 是否存在高延时的I/O 离群点?
- I/O 延时的全分布是什么样的?
- 是否有例如I/O 流控的系统资源控制,存在并且激活了吗?
- 非数据传输磁盘命令的延时是多少?
延时分析
发起I/O 请求和完成中断之间的时间。如果这个时间与应用程序感受到的I/O 延时一致,通常可以安全地推断出I/O 延时源于磁盘
例如,B 的延时看上去来自文件系统层(锁或者队列?),因为底层的I/O 延时只占了较小的部分
事件跟踪
- 磁盘设备ID。
- I/O 类型:读或写。
- I/O 偏移量:磁盘位置。
- I/O 大小:字节数。
- I/O 请求时间戳:I/O 发到设备的时间(也称为I/O 策略)。
- I/O 完成时间戳:I/O 事件完成的时间(完成中断)。
- I/O 完成状态:错误。
网络
除了改进网络延时和吞吐量以外,另一个常见的任务是消除可能由丢包引起的延时异常
网络分析是跨硬件和软件的。这里的硬件指的是物理网络,包括网络接口卡、交换机,路由器和网关(这通常也含有软件)。这里的系统软件指的是内核协议栈,通常是TCP/IP,以及每个所涉及的协议的行为
网络工程师解惑篇,推荐这个可以在空闲充电的音频,理解基本的网络协议和架构
概念和架构
-
网络接口和控制器,网络接口卡(NIC)给系统提供一个或多个网络端口并且设有一个网络控制器,一个在端口与系统I/O 传输通道间传输包的微处理器
-
协议栈,不同的IP 协议版本IPv4 和IPv6,可能会由不同的内核代码路径处理,进而表现出不同的性能特性
-
路由,路由管理称为包的报文跨网络传递
-
单播和多播,成对主机间由单播(unicast)传输连接。多播(multicast)传输允许一个发送者可能跨越多个网络同时传输给多个目标。它的传递依赖于路由器配置的支持
-
包长度,包的长度通常受限于网络接口的最大传输单元(MTU)长度。巨型帧(9000B)够提高网络吞吐性能
-
延时,包括主机名解析延时、ping 延时、连接延时、首字节延时、往返时间,以及连接生命周期
- ping延时,ICMP echo 请求到echo 响应所需的时间,衡量主机对之间包括网络跳跃的网络延时,而且它测量的是包往返的总时间
- 连接延时,是传输任何数据前建立网络连接所需的时间
- 首字节延时,是从连接建立到接收到第一个字节数据所需的时间。这包括远程主机接受连接、调度提供服务的线程,并且执行线程以及发送第一个字节所需的时间
- 往返时间,指网络包往返两个端点所需的时间
- 连接生命周期,指一个网络连接从建立到关闭所需的时间
-
缓冲,TCP 利用缓冲以及可变的发送窗口提升吞吐量。网络套接字也保有缓冲,并且应用程序可能也会利用它们自己的缓冲在发送前聚集数据。外部的网络组件,如交换机和路由器,也会利用缓冲提高它们的吞吐量
-
接口协商,通过与对端自动协商,网络接口能够工作于不同的模式,包括带宽和双工模式
谷歌开源、高性能RPC框架:gRPC 使用体验
-
协议,TCP和UPD协议的特性
-
硬件,包括接口,控制器,路由器和交换机
-
软件,内核协议栈
现代内核中网络栈是多线程的,并且传入的包能被多个CPU 处理。传入的包与CPU 的映射可用多个方法完成:可能基于源IP 地址哈希以平均分布负载,或者基于最近处理的CPU 以有效利用CPU 缓存热度以及内存本地性
TCP、IP 以及通用网络驱动软件是内核的核心组件,而设备驱动程序是附加模块。数据包以struct sk_buff 数据类型穿过这些内核组件
-
TCP积压队列,突发的连接由积压队列处理。这里有两个此类队列,一个在TCP 握手完成(也称为SYN积压队列)前处理未完成的连接,而另一个处理等待应用程序接收(也称为侦听积压队列)的已建立的会话
-
TCP缓冲区,发送和接受缓冲区的大小是可调的,但需要消耗更多主存,大缓冲能够提升吞吐量
网络设备和网络接受的数据包大小最大到MSS最大段大小,可能小到1500字节
-
网卡的发送和接收,发送数据包网卡收到通知,并使用DMA从内核内存中读取帧提高效率。收到的数据包使用DMA放入内核环形缓冲区中,并中断通知内核
具体分析方法
常用工具
- netstat -s:查找高流量的重新传输和乱序数据包。哪些是“高”重新传输率依客户机而不同,面向互联网的系统因具有不稳定的远程客户会比仅拥有同数据中心客户的内部系统具有更高的重新传输率。
- ip -s link/netstat -i:检查接口的错误计数器,检查“错误”、“丢弃”、“超限”
- ss -tiepm,检查重要套接字的限制其标志
- ip -s link/nicstat,检查传输和接受字节的速率
- tcplife,记录tcp会话的进程细节
- tcptop,实时观测速率最高的tcp会话
- tcpdump,抓包
- dtrace/stap/perf:用来检查包括内核状态在内的应用程序与线路间选中的数据
USE方法
- 使用率:接口忙于发送或接收帧的时间。
- 饱和度:由于接口满负载,额外的队列、缓冲或者阻塞的程度。
- 错误:对于接收,校验错误、帧过短(小于数据链路报文头)或者过长、冲突(在交换网络中不大可能);对于传输,延时碰撞(线路故障)
负载特征归纳
- 网络接口吞吐量:RX 和TX,B/s。
- 网络接口IOPS:RX 和TX,帧每秒。
- TCP 连接率:主动和被动,每秒连接数。
- 平均数据包的大小是多少?RX、TX?
- 协议是什么?TCP 还是UDP?
- 活跃的TCP/UDP 端口是多少?B/s、每秒连接数?
- 哪个进程在主动地使用网络?
延时分析
延时 | 描述 |
---|---|
主机名解析 | |
ping延时 | |
tcp连接初始化延时 | |
tcp首字节延时 | |
tcp重传输 | |
tcp TIME_WAIT延时 | |
连接会话延时 | |
系统调用发送/接收延时 | |
系统调用连接延时 | |
网络往返延时 | |
中断延时 | |
栈间延时 | 数据包在内核栈种移动的时间 |
静态性能调优
- 有多少网络接口可供使用?当前使用中的有哪些?
- 网络接口的最大速度是多少?
- 当前协商的网络接口速度是多少?
- 网络接口协商为半双工还是全双工?
- 网络接口配置的MTU 是多少?
- 网络接口是否使用了链路聚合?(多个网卡提升带宽)
- 有哪些适用于设备驱动的可调参数?IP 层?TCP 层?
- 有哪些可调参数已不再是默认值?
- 路由是如何配置的?默认路由是什么?
- 数据路径中网络组件的最大吞吐量是多少(所有组件,包括交换机和路由器背板)?
- 数据转发是否启用?该系统是否作为路由器使用?
- DNS 是如何设置的?它距离服务器有多远?
- 该版本的网络接口固件是否有已知的性能问题(bug)?
- 该网络设备驱动是否有已知的性能问题(bug)?内核TCP/IP 栈呢?
- 是否存在软件施加的网络吞吐量限制(资源控制)?它们是什么?