CPU执行指令集中指令,指令执行步骤由功能单元处理,包括指令预取、解码、执行、内存访问(可选)、寄存器写回(可选) 。每步至少需一个时钟周期,内存访问最慢,常需数十个时钟周期,期间指令执行停滞,产生停滞周期,凸显CPU缓存重要性。

6.3.3 指令流水线

一种CPU架构,通过同时执行不同指令的不同部分,实现多指令同时执行,类似工厂组装线提高吞吐量 。无流水线时,指令步骤依次执行需多个周期,流水线可激活多个功能单元,理想情况一个周期完成一条指令执行。

6.3.4 指令宽度

同类型功能单元可多个并存,使每个时钟周期处理更多指令,此CPU架构为超标量,常与流水线结合提高指令吞吐量 。指令宽度指同时处理的目标指令数量,现代处理器一般为宽度3或4,即每周期最多完成3 - 4个指令,实现方式因处理器而异。

6.3.5 CPI,IPC

每指令周期数(CPI)是重要高级指标,描述CPU时钟周期使用情况及理解CPU使用率本质,每周期指令数(IPC)是其倒数 。CPI高表示CPU常停滞(多在访问内存),CPI低则指令吞吐量高,是性能调优方向 。内存访问密集负载会提高CPI,即便用更快内存或CPU,若内存I/O耗时不变,也难达预期性能 。CPI与处理器及功能有关,可实验得出,如高CPI负载下CPI可达10或更高,低CPI负载下可低于1(得益于指令流水线和宽度技术) 。需注意,CPI代表指令处理效率,不代表指令本身效率,低效循环操作寄存器虽可能降低CPI,但会增加CPU使用和利用度。

6.3.6 使用率

CPU使用率是一段时间内CPU实例忙于执行工作的时间比例,以百分比表示 。可测量CPU未运行内核空闲线程的时间,期间CPU可能运行应用程序线程等 。高使用率不代表有问题,仅表明系统在工作,有人视其为ROI指示器 。内核支持优先级、抢占和分时共享,高使用率下性能不一定显著下降 。测量包含符合条件活动的时钟周期(含内存停滞周期) ,常分成内核时间和用户时间两个指标。

6.3.7 用户时间/内核时间

用户时间指CPU执行用户态应用程序代码的时间,内核时间指执行内核态代码的时间(含系统调用、内核线程和中断时间) 。用户时间和内核时间之比能揭示负载类型,计算密集型应用用户/内核时间比接近99/1 ,I/O密集型应用(如Web服务器)约为70/30 ,具体比例依多种因素而定。

6.3.8 饱和度

100%使用率的CPU处于饱和状态,线程会遇调度器延时,因需等待在CPU上运行,降低总体性能 。另一种饱和度形式与CPU资源控制有关,在云计算环境中,即使CPU未达100%使用,但达到控制上限,可运行线程也需等待 。饱和的CPU问题不如其他资源严重,因高优先级工作可抢占当前线程。

6.3.9 抢占

允许更高优先级的线程抢占当前正在运行的线程并开始执行,可节省高优先级工作的运行队列延时时间,提高性能 。

6.3.10 优先级反转

低优先级线程拥有资源,阻碍高优先级线程运行的情况,降低高优先级工作性能 。Solaris内核实现优先级继承机制避免此问题,Linux从2.6.18起提供支持优先级继承的用户态mutex用于实时负载 。

6.3.11 多进程,多线程

多数处理器支持多个CPU,应用程序利用此功能需开启不同执行线程并发运行,在多CPU上扩展技术分为多进程和多线程 。Linux可使用这两种模型,由任务实现 。多进程开发较简单(用fork() ),内存开销大(不同地址空间),CPU开销大(fork()/exit()及MMU管理开销),通信通过IPC(有CPU开销),内存使用有冗余但进程结束可返还内存;多线程使用线程API ,内存开销小,CPU开销小(API调用),通信直接(共享内存加同步原语),内存使用可能因CPU竞争和碎片化有问题 。多线程一般被认为优于多进程,但开发难度大 。重要的是创建足够进程或线程占据预期数量CPU以最大化性能,不过部分应用在更少CPU上可能因线程同步和内存本地性问题跑得更快。

6.3.12 字长

处理器按最大字长(32位或64位 )设计,字长表示整数大小、寄存器宽度、地址空间大小和数据通路宽度 。更宽字长一般性能更好,但可能在某些数据类型下因未使用位产生额外内存开销,数据大小也会因指针增大而增加,导致更多内存I/O 。对64位x86架构,寄存器增加和有效调用约定抵消开销,64位应用比32位版本快 。处理器和操作系统支持多种字长,软件编译成较小字长可能成功运行但速度慢。

6.3.13 编译器优化

应用程序在CPU上的运行时间可通过编译器选项(含字长设置)大幅改进 。编译器不断更新以利用最新CPU指令集及其他优化,使用新编译器有时可显著提高应用程序性能,第5章有详细叙述。

6.4 架构

6.4.1 硬件
  • 处理器:通用双核处理器组件中,控制器(控制逻辑)是核心,负责指令预取、解码、执行管理及结果存储 。处理器还包含共享浮点单元、(可选)共享三级缓存,其他相关组件有P - cache(预取缓存,每CPU一个 )、W - cache(写缓存,每CPU一个 )、时钟(CPU时钟信号生成器或外部提供 )、时间戳计数器(由时钟递增实现高精度计时 )、微代码ROM(快速将指令转化为电路信号 )、温度传感器(用于温度监控,部分处理器如Intel睿频加速技术会以此为单核动态超频输入 )、网络接口(高性能芯片集成 )。

《性能之巅》第六章 CPU_优先级

  • CPU缓存:多种硬件缓存可集成在处理器内或外置,通过更快内存缓存读写提升内存性能 。包括一级指令缓存(I )、转译后备缓冲器(TLB )、二级缓存(E

《性能之巅》第六章 CPU_寄存器_02

《性能之巅》第六章 CPU_缓存_03

延时

多级缓存用于平衡大小和延时,一级缓存访问时间通常为几个CPU时钟周期,二级缓存约几十个时钟周期 。主存访问耗时约60ns(4GHz处理器约240个周期 ),MMU地址转译会增加额外延时 。可通过微型基准测试(如LMbench )测出CPU缓存延时特征,图6.7展示了Intel Xeon E5620 2.4 GHz处理器在不同内存范围下的内存访问延时情况,当某级缓存被填满,访问延时会增加到下一级(更慢)缓存水平。

《性能之巅》第六章 CPU_#服务器_04

相联性

相联性是定位缓存新条目的特性,类型包括:

  • 全关联:缓存可在任意位置放置新条目,如LRU算法可剔除缓存中最旧条目 。
  • 直接映射:每个条目在缓存中只有一个有效位置,通过对内存地址哈希确定缓存地址 。
  • 组关联:先通过映射(如哈希)定位缓存中一组地址,再用算法(如LRU)选择,如四路组关联将地址映射到四个可能位置并挑选合适的 。CPU缓存常采用组关联,平衡全关联开销大与直接映射命中率低的问题。
缓存行

缓存行大小是CPU缓存特征,是存储和传输的字节数量单位,可提高内存吞吐量 ,x86处理器典型缓存行大小为64字节 ,编译器和程序员优化性能时会考虑此参数。

缓存一致性

内存可能同时被不同处理器的多个CPU缓存,当一个CPU修改内存,需确保其他缓存知晓其拷贝失效并丢弃,以保证后续读取新修改内容,此为缓存一致性 ,是设计可扩展多处理器系统的挑战之一,因内存频繁修改。

MMU(内存管理单元)

负责虚拟地址到物理地址转换 。图6.8展示普通MMU及相关CPU缓存类型,通过芯片上的TLB缓存地址转换 。主存(DRAM)中的转换表(页表)处理缓存未命中情况,由MMU硬件直接读取 。老处理器通过软件遍历页表处理TLB未命中,维护转译存储缓冲区(TSB);新处理器可通过硬件响应TLB未命中,降低开销。

《性能之巅》第六章 CPU_缓存_05

硬件 - 互联部分

互联方式

在多处理器架构中,处理器通过共享系统总线(如早期Intel处理器使用的前端总线 )或专用互联(如Intel的快速通道互联QPI、AMD的HyperTransport HT )连接 。互联不仅连接处理器,还可连接I/O控制器等组件 。这与系统内存架构(统一内存访问UMA或非统一内存访问NUMA )相关,第7章会讨论。

共享系统总线问题

使用共享系统总线(前端总线)时,随着处理器数量增加,会因共享总线资源出现扩展性问题 。

《性能之巅》第六章 CPU_缓存_06

专用互联优势

现代服务器多为多处理器且采用NUMA架构,使用专用CPU互联技术 。处理器间的私有连接(如QPI )提供无竞争访问和更高带宽 。以Intel FSB(2007年,传输率1.6 GT/s ,宽度8字节,带宽12.8 GB/s )和QPI(2008年,传输率6.4 GT/s ,宽度2字节,带宽25.6 GB/s )为例,QPI速率是FSB两倍,在时钟两个边缘传输数据,使数据传输率加倍 。此外,处理器还有用于核间通信的内部互联 。互联通常设计为高带宽,避免成为系统瓶颈,若成为瓶颈,涉及互联的CPU指令(如远程内存I/O )会停滞,表现为CPI上升,可通过CPU性能计数器分析CPU指令、周期、CPI、停滞周期和内存I/O等情况。

《性能之巅》第六章 CPU_优先级_07

CPU性能计数器

定义与别名:CPU性能计数器(CPC),也叫性能测量点计数器(PIC)、性能监控单元(PMU)、硬件事件和性能监控事件 ,是能计数低级CPU活动的处理器寄存器。
包含计数器类型

  • CPU周期:含停滞周期及类型。
  • CPU指令:统计执行过(引退)的指令。
  • 缓存访问:涉及一级、二级、三级缓存的命中与未命中情况。
  • 浮点单元:记录操作情况。
  • 内存I/O:涵盖读、写及停滞周期。
  • 资源I/O:包含读、写及停滞周期。
    寄存器情况:每个CPU通常有2 - 8个可编程记录事件的寄存器,具体取决于处理器类型和型号,可在处理器手册中查阅 。以Intel P6家族处理器为例,通过四个型号特定寄存器(MSR)提供性能寄存器,两个为只读计数器,两个用于对计数器编程(事件选择MSR ,可读可写 ),性能计数器为40b寄存器,事件选择MSR是32b 。事件选择确定计数事件类型,UMASK确定子类型或子类型组 ,OS和USR位选择计数器在内核模式(OS)或用户模式(USR)递增,CMASK设置事件阈值,达到阈值后计数器递增 。
    事件示例:Intel处理器手册列出可通过事件选择和UMASK值计数的众多事件,表6.4展示部分示例,如DATA_MEM_REFS记录从所有类型内存的读取和存储(不包括I/O及非内存访问 )、DCU_MISS_OUSTANDING统计DCU未命中期间的周期权重数等 。
    新处理器特性:新处理器(如Intel Sandy Bridge家族 )有更多类型计数器和寄存器,每个硬件线程有三个固定和四个可编程计数器,每个核还有八个额外可编程计数器(“通用” ),读取时为48位 。
    标准接口:不同厂商性能处理器不同,处理器应用程序员接口(PAPI)提供一致接口,为计数器类型取通用名字,如PAPI_tot_cyc代表总周期数。

《性能之巅》第六章 CPU_缓存_08

《性能之巅》第六章 CPU_#服务器_09

《性能之巅》第六章 CPU_寄存器_10

6.4.2 软件

支撑CPU的内核软件组成:包括调度器、调度器类和空闲线程 。

《性能之巅》第六章 CPU_缓存_11

调度器功能

  • 分时:在可运行线程间分配任务,优先执行最高优先级任务 。
  • 抢占:高优先级线程变为可运行状态时,能抢占当前运行线程,使其马上开始运行 。
  • 负载均衡:将可运行线程移至空闲或较不繁忙的CPU队列 。 每个优先级有自己的运行队列,便于调度器管理CPU运行队列及线程 。
    Linux内核调度情况
  • 实现方式:分时通过系统时钟中断调用scheduler_tick()实现,该函数调用调度器类管理优先级和CPU时间片到期事件 。线程状态变为可运行时触发抢占,调用调度类函数check_preempt_curr() ,线程切换由__schedule()管理,后者通过pick_next_task()选择最高优先级线程 ,负载均衡由load_balance()函数负责 。
  • 调度器类
  • RT:为实时类负载提供固定高优先级,支持用户和内核级别抢占,允许RT任务短延时分发,优先级范围0 - 99(MAX_RT_PRIO-1 ) 。
  • O(1):在Linux 2.6作为默认用户进程分时调度器引入,名字源于算法复杂度O(1) ,相对于CPU消耗型线程,能动态提高I/O消耗型线程优先级,降低交互和I/O负载延时 。
  • CFS:Linux 2.6.23引入完全公平调度作为默认用户进程分时调度器,使用红黑树管理任务,以任务CPU时间作为键值,使CPU少量消费者更易被找到,提高交互和I/O消耗型负载性能 。
  • 调度器策略:用户级进程可通过调用sched_setscheduler()设置调度器策略调整调度类行为 。RT类支持SCHED_RR(轮转调度,线程用完时间片移至同优先级运行队列尾部 )和SCHED_FIFO(先进先出调度,运行队列头线程直到其自愿退出或更高优先级线程抵达 )策略;CFS类支持SCHED_NORMAL(以前称SCHED_OTHER ,分时调度,调度器根据调度类动态调整优先级 )和SCHED_BATCH(类似SCHED_NORMAL ,期望线程为CPU消耗型,不打断I/O消耗型交互工作 )策略 。
  • Solaris内核调度情况
  • 实现方式:分时由clock()驱动,调用包括ts_tick()在内的调度类函数检查时间片是否到期 。线程超分配时间,优先级降低,允许其他线程抢占 。preempt()处理用户线程抢占,kpreempt()处理内核线程抢占 。switch()函数处理线程上下文切换,并调用分发器函数寻找替代运行的最佳可运行线程 ,负载均衡调用类似函数寻找其他CPU分发器(运行队列)的空闲线程 。
  • 调度器类:管理可运行线程行为,包括优先级、CPU时间分配及时间片长度(时间量子 ) ,还可通过调度策略对同一优先级线程进行控制 。用户线程优先级受用户定义的nice值影响,可降低不重要工作优先级 。需注意,Linux和Solaris内核优先级范围相反,Linux继承原来UNIX把较小数字用于高优先级线程的方式 。
    空闲线程:当没有线程可运行时,空闲任务(空闲线程)运行,直至有其他线程可运行 。

Solaris内核调度相关内容

调度器类

  • RT(实时调度):为实时负载提供固定高优先级,可抢占除中断服务外的其他工作,确保应用程序响应时间可确定,适用于实时工作负载。
  • SYS(系统调度):内核线程的高优先级调度类,线程有固定优先级,可按需持续执行,直至被RT或中断抢占 。
  • TS(分时调度):用户进程默认调度类,依据最近CPU用量动态调整优先级和时间片 。CPU消耗型负载因使用时间片优先级降低、时间片增加,以低优先级运行;I/O消耗型负载在自愿切换上下文时以高优先级运行,性能不受长时间占用CPU任务影响,nice值也会对其产生影响 。
  • IA(交互调度):与TS类似,但默认优先级稍高,现较少使用,曾用于提高图形X会话响应度 。
  • FX(固定调度):设定固定优先级的进程调度类,全局优先级范围与TS相同(0 - 59 )。
  • FSS(公平共享调度):基于共享值管理一组(项目或区域)进程的CPU用量,允许组公平分享CPU资源,用量基于线程或进程数量,进程组可消耗的CPU资源大小与份额值及系统繁忙份额总数相关,常用于云计算,与TS有相似的全局优先级范围(0 - 59 )和固定时间片 。
  • SYSIDLE:系统工作调度类,为特定系统线程(如ZFS事务组刷新进程 )准备,可指定目标工作周期(CPU时间占运行时间比率 ),符合条件时调度线程,避免内核线程长时间运行,否则会反向睡眠 。
  • 中断:调度中断线程,优先级为159 + IPL(见第3章3.2.3节 ) 。
    调度策略:基于Solaris的系统支持通过sched_setscheduler()设置调度策略,如SCHED_FIFO、SCHED_RR和SCHED_OTHER(分时 ) 。
    空闲线程:内核“空闲”线程在无其他可运行线程时运行,以最低优先级执行,常通知处理器停止CPU执行(停止指令 )或减速以节省资源,下次硬件中断时唤醒 。

NUMA分组

  • 在Linux系统,内核感知NUMA可优化调度和内存分配,自动检测并建立本地化的CPU和内存资源组,依据NUMA架构拓扑分组,从预估内存访问开销的根开始 。
  • 在Solaris系统,本地组(lgrps )以根组为起点,系统管理员可手动分组,将进程绑定到一个或多个CPU,也可创建CPU组并分配进程 。
    处理器资源感知:与NUMA不同,内核可理解CPU资源拓扑结构,在Solaris系统中由处理器组实现,用于资源管理和负载均衡,做出更好的调度决策。

6.5 CPU 分析和调优方法

本节介绍 CPU 分析和调优的多种方法,这些方法可单独或结合使用,建议使用顺序为性能监控、USE 方法、剖析、微型基准测试和静态分析 ,后续 6.6 节将展示如何用操作系统工具应用这些策略。表 6.5 对主要方法分类总结:

方法

类型

工具法

观察分析

USE 方法

观察分析、容量规划

负载特征归纳

观察分析

剖析

观察分析

周期分析

观察分析

性能监控

观察分析、容量规划

静态性能调优

观察分析、容量规划

优先级调优

调优

资源控制

调优

CPU 绑定

调优

微型基准测试

实验分析

扩展

容量规划、调优

工具法
通过使用各类工具检查关键指标来分析 CPU 。涉及工具及检查项目:

  • uptime:查看负载平均数判断 CPU 负载随时间变化趋势,负载平均数超 CPU 数量常表示 CPU 饱和。
  • vmstat:每秒运行检查空闲时间,低于 10% 可能存在问题。
  • mpstat:检查单个繁忙 CPU ,排查线程扩展性问题。
  • top/prstat:找出进程和用户中 CPU 消耗大户。
  • pidstat/prstat:将 CPU 消耗大户分解为用户和系统时间。
  • perf/dtrace/stap/oprofile:从用户或内核时间角度剖析 CPU 使用堆栈跟踪。
  • perf/cpustat:测量 CPI 。

USE 方法
在性能调查早期使用,用于发现所有组件内 CPU 瓶颈和错误 。检查内容:

  • 使用率:CPU 繁忙时间(非空闲线程中)。
  • 饱和度:可运行线程排队等待 CPU 的程度。
  • 错误:CPU 错误,包括可改正错误。可优先检查错误,部分处理器和操作系统能感知可改正错误(如 ECC ),并在不可改正错误致 CPU 失效前关闭 CPU 警示,还需检查所有 CPU 是否在线。使用率可从系统工具繁忙百分比获取,用于发现扩展性问题及理解高 CPU 和核使用率原因。在如云计算环境中,CPU 使用率需关注配额限制,饱和度指标量化 CPU 过载或配额消耗程度。

负载特征归纳
对施加负载进行特征归纳,是容量规划、基准测试和模拟负载重要步骤,可发现可剔除无用工作以提升性能。

  • 基本属性:平均负载(使用率 + 饱和度)、用户时间与系统时间之比、系统调用频率、自愿上下文切换频率、中断频率 。工作目的是归纳负载特征而非性能结果,平均负载反映 CPU 请求负载,更适合归纳。频率指标理解较难,但能反映负载类型,高用户时间与内核时间比例为计算型负载,高系统调用和中断频率可能为 I/O 消耗型负载。
  • 高级负载特征归纳/检查清单:涵盖整个系统及单个 CPU 使用率、负载并发程度、使用 CPU 的应用程序/用户/内核线程、中断及 CPU 互联使用率、CPU 使用原因、停滞周期类型等问题 。该方法概要及测量特征相关内容参见第 2 章,后续章节会扩展剖析分析调用路径和周期分析研究停滞周期相关内容。
6.5.4 剖析Profiling

剖析是通过定期取样 CPU 状态来构建研究目标图像,以分析 CPU 用量 。

剖析步骤

  1. 选择要采集的剖析数据及频率。
  2. 按设定时间间隔开始取样。
  3. 等待关注的兴趣事件发生。
  4. 停止取样并收集数据。
  5. 对收集的数据进行处理分析。部分剖析工具(如 DTrace )支持实时处理采集数据,一边采样一边分析。同时,借助如火焰图等工具可提升处理和浏览剖析数据的能力;像 Oracle Solaris Studio 的性能分析器能自动化收集过程,方便对照目标源代码浏览剖析数据。

剖析数据类型影响因素

  • 级别:涵盖用户级别、内核级别或两者兼具 。
  • 功能及偏移量:包括基于程序计数器的仅功能、部分栈跟踪信息、全栈跟踪等情况 。选择全栈跟踪加上用户和内核级别虽能采集完整 CPU 用量信息,但会产生大量数据;仅采集用户或内核级别、部分栈(如五层栈),甚至仅执行函数名,可从较少数据推断 CPU 用量。

剖析示例

文中给出 DTrace 单行命令示例,以 997Hz 频率采集 10s 内用户级函数名,命令执行后通过合计函数名处理数据并按频率打印排序计数,结果展示了 CPU 上执行最频繁的用户级函数。997Hz 采样频率是为避免与 100Hz 或 1000Hz 定时运行任务合拍。通过采样全栈跟踪,能发现 CPU 使用的代码路径,从更高视角审视 CPU 用量。更多采样例子见 6.6 节,有关 CPU 剖析从栈中获取其他编程语言上下文信息细节见第 5 章 。此外,对于缓存和互联等特殊 CPU 资源,剖析可使用基于 CPC 的事件触发器,相关内容在周期分析部分介绍。

6.5.5 周期分析

利用 CPU 性能计数器(CPC)从周期级别理解 CPU 使用率,可展示一级、二级或三级缓存未命中、内存 I/O、资源 I/O 上的停滞周期,以及浮点操作等活动的周期 。通过分析这些信息,可调整编译器选项或修改代码提升性能。

  • 分析流程:从测量 CPI 入手,CPI 较高时调查停滞周期类型;CPI 较低时寻求减少指令数量的方法。CPI 的高低因处理器而异,一般小于 1 为低,大于 10 为高,可通过已知负载(内存 I/O 密集型或指令密集型)感知其范围 。
  • CPC 配置:除测量计数器值,还能配置 CPC 在超出特定值时中断内核。如每 10000 次二级缓存未命中中断一次内核获取栈回溯,随时间推移,内核可建立造成二级缓存未命中的大致代码路径,避免每次未命中都测量的高开销。集成开发环境(IDE)常利用此功能标注造成内存 I/O 和停滞周期的代码位置,使用 DTrace 和 CPC provider 也能实现类似观察效果 。周期分析是高级活动,可能需借助命令行工具花费数天,也可参考 CPU 供应商处理器手册,性能分析器(如 Oracle Solaris Studio )能辅助找到感兴趣的 CPC 。
6.5.6 性能监控

用于发现一段时间内活跃的问题和行为模式 。关键 CPU 指标包括:

  • 使用率:即繁忙百分比。
  • 饱和度:由系统负载推算出的运行队列长度或线程调度器延时数值 。需对每个 CPU 分别监控使用率,以发现线程扩展性问题;在有 CPU 限制或配额的环境(如云计算环境),要记录相对于限制的 CPU 用量 。监控 CPU 用量时,选择合适测量和归档时间间隔是挑战,5 分钟间隔可能忽视短时间 CPU 使用率突发,理想是每秒测量,但突发也可能在 1 秒内发生,相关信息可从饱和度获取 。
6.5.7 静态性能调优

关注配置环境问题,针对 CPU 性能,检查以下静态配置:

  • CPU 数量、是核还是硬件线程。
  • CPU 架构为单处理器还是多处理器。
  • CPU 缓存大小及是否共享。
  • CPU 时钟频率(如 Intel 睿频加速和 SpeedStep 等动态特性)在 BIOS 中是否启用。
  • BIOS 中启用或禁用的其他 CPU 相关特性。
  • 处理器是否存在性能问题(如在处理器勘误表中列出的问题)。
  • BIOS 固件版本的性能问题。
  • 是否存在软件的 CPU 使用限制(资源控制)及具体限制内容 。这些问题答案有助于发现之前忽视的配置选择,云计算环境需特别关注 CPU 使用限制问题 。
6.5.8 优先级调优

UNIX 提供 nice() 系统调用,通过设置 nice 值调整进程优先级 。正值降低进程优先级(更友好),负值(仅超级用户 root 可设置)提高优先级 。nice(1) 命令指定 nice 值启动程序,renice(1M) 命令(BSD 上)调整已运行进程优先级 。在 CPU 竞争时,为高优先级工作制造调度器延时,找出低优先级工作(如监控代理程序、定期备份等),以合适 nice 值启动并分析调优效果,确保高优先级工作调度器延时低 。此外,操作系统还提供更改调度类、调度器策略或类调优等更高级进程优先级控制方式,基于 Linux 和 Solaris 的系统含实时调度类,允许进程抢占其他工作,但需注意实时应用程序若有缺陷(如线程陷入无限循环)可能导致所有进程无法使用 CPU,通常需重启系统修复 。

6.5.8 资源控制

操作系统可对进程或进程组分配 CPU 资源进行细粒度控制,包括 CPU 使用率固定限制和灵活共享方式(基于共享值消耗空闲 CPU 周期),运作原理在 6.8 节讨论 。

6.5.9 CPU 绑定

将进程和线程绑定在单个或一组 CPU 上,可增加进程 CPU 缓存热度,提升内存 I/O 性能,对 NUMA 系统可提高内存本地性 。实现方式有:

  • 进程绑定:配置进程只在单个 CPU 或预定义 CPU 组中运行。
  • 独占 CPU 组:划分一组 CPU 专供指定进程使用,提升 CPU 缓存效率,避免进程空闲时其他进程占用 CPU 导致缓存热度降低 。基于 Linux 系统可通过 cpuset 实现独占 CPU 组,基于 Solaris 系统称为处理器组,6.8 节有配置示例 。
6.5.10 微型基准测试

多种工具测量简单操作多次执行时间,操作基于以下元素:

  • CPU 指令:涵盖整数运算、浮点操作、内存加载存储、分支等指令。
  • 内存访问:调查不同 CPU 缓存延时和主存吞吐量。
  • 高级语言:类似 CPU 指令测试,用高级解释或编译语言编写。
  • 操作系统操作:测试如 getpid()、进程创建等 CPU 消耗型系统库和系统调用函数 。早期如美国国家物理实验室 1972 年用 Algol 60 编写的 Whetstone 模拟科学计算负载,1984 年的 Dhrystone 模拟整数负载并成为流行 CPU 性能比较方法,多种 UNIX 基准测试(如进程创建、管道吞吐量等)集合在 UnixBench 中 。近期测试包含压缩速度、质数计算、加密编码等 。比较系统结果时,需明确测试对象,很多基准测试是编译优化结果而非 CPU 速度测试,且多为单线程执行,在多 CPU 系统中意义有限 ,更多信息见第 12 章 。
6.5.11 扩展

基于资源的容量规划扩展方法:

  1. 确定目标用户数或应用程序请求频率。
  2. 对现有系统,通过监控 CPU 用量除以用户数或请求数,转化为每用户或每请求 CPU 使用率;未投入使用系统用负载生成工具模拟用户获取 CPU 用量 。
  3. 推算 CPU 资源达 100% 使用率时的用户或请求数,即系统理论上限 。对系统扩展性建模,考虑竞争和一致性延时条件,可获更实际性能预测,相关建模和扩展信息分别见第 2 章 2.6 节和 2.7 节 。

6.6 CPU性能分析工具介绍

本节介绍基于Linux和Solaris系统的CPU性能分析工具,相关工具及对应功能如下表:

Linux

Solaris

描述

uptime

uptime

平均负载

vmstat

vmstat

包括系统范围的CPU平均负载

mpstat

mpstat

单个CPU统计信息

sar

sar

历史统计信息

ps

ps

进程状态

top

prstat

监控每个进程/线程CPU用量

pidstat

prstat

每个进程/线程CPU用量分解

time

ptime

给一个命令计时,带CPU用量分解

DTrace、perf

DTrace

CPU剖析和跟踪

perf

cpustat

CPU性能计数器分析

这些工具从提供CPU统计信息,到深入进行代码路径剖析和CPU周期分析。完整功能说明可参考各工具文档及Man手册。建议了解不同操作系统工具,因其能从多角度反映系统信息。

6.6.1 uptime工具

uptime(1) 是用于打印系统平均负载的工具之一 。通过命令行输入uptime可获取系统当前时间、系统已运行天数、当前登录用户数以及1、5和15分钟内的平均负载 。例如输出9:04pm up 268 day(s), 10:16, 2 users, load average: 7.76, 8.32, 8.60,其中最后三个数字分别代表1、5和15分钟内平均负载 。

《性能之巅》第六章 CPU_寄存器_12

  • 平均负载含义及计算:平均负载表示对CPU资源的需求,由正在运行的线程数(使用率)和正在排队等待运行的线程数(饱和度)汇总计算得出。新计算方法是将使用率加上线程调度器延时,可提高精度 。平均负载大于CPU数量,意味着CPU不足以服务线程,部分线程在等待;平均负载小于CPU数量,则表明系统还有余量,线程可按需在CPU上运行 。
  • 平均负载特性:这三个平均负载值是指数衰减移动平均数,反映了1、5和15分钟以上(实际是指数移动总和中使用的常数)的负载 。早期BSD将平均负载引入UNIX,当时基于调度器平均队列长度计算,在早期操作系统(CTSS、Multics、TENEX )中广泛使用,在[RFC 546]中有提及 。以TENEX为例,平均负载是对CPU需求的度量,是一段时间内可运行进程数的平均值。如单CPU系统一小时内平均负载为10,意味着该小时内任意时刻预期有1个进程在运行,9个就绪进程等待CPU 。现代例子中,64颗CPU的系统平均负载为128,即平均每个CPU上有一个线程运行,一个线程等待;若平均负载为10,则系统余量较大,所有CPU跑满前还可运行54个CPU消耗型线程 。

Linux平均负载相关问题

在Linux系统中,当前平均负载的计算包含了处于不可中断状态执行磁盘I/O的任务 。这使得平均负载不能单纯用来表示CPU余量或饱和度,无法仅依据该值推断CPU或磁盘负载情况。因为负载在CPU和磁盘间动态变化,比较1、5、15分钟这三个平均负载数值也变得困难 。

为解决包含其他资源负载的问题,一种思路是针对不同类型资源(如磁盘、内存、网络等)分别设置平均负载 。相关原型实践表明,各资源拥有独立的平均负载数值,能为非CPU资源状况提供有效概览 。

鉴于上述情况,在Linux上了解CPU负载时,不宜仅依赖平均负载,最好借助其他指标,如vmstat(1)和mpstat(1)工具提供的数据 。

6.6.2 vmstat工具

vmstat(8) 是虚拟内存统计信息命令,可打印系统全局范围的CPU平均负载,第一列还显示可运行线程数 。以Linux版本示例输出为例:

$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
15  0 2852 46686812 279456 1401196    0    0     0     0    0    0  0  0 100  0  0
16  0 2852 46685192 279456 1401196    0    0     0     0 2136 36607 56 33 11  0  0
15  0 2852 46685952 279456 1401196    0    0     0    56 2150 36905 54 35 11  0  0
15  0 2852 46685960 279456 1401196    0    0     0     0 2173 36645 54 33 13  0  0
[...]

输出第一行是系统启动以来的总结信息(Linux 上的 r 列显示当前值除外 )。各列含义如下:

  • r:运行队列长度,即所有等待加上正在运行的线程数(Linux );在 Solaris 上仅指在分派队列里等待的线程数 。
  • us:用户态时间。
  • sy:系统态时间(内核)。
  • id:空闲。
  • wa:等待I/O,即线程阻塞等待磁盘I/O时的CPU空闲时间。
  • st:偷取(虚拟化环境下CPU在其他租户上的开销,未在输出显示 )。这些值除 r 外是所有 CPU 的系统平均数 。
6.6.3 mpstat工具

多处理器统计信息工具mpstat可报告每个CPU的统计信息 。

  • Linux版本:使用-P ALL选项打印每个CPU报告,默认打印系统级别总结信息 。示例输出如下:
$ mpstat -P ALL 1
02:47:49 CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest   %idle
02:47:50 all    54.37    0.00   33.12    0.00    0.00    0.00    0.00    0.00   12.50
02:47:50 0      22.00    0.00   57.00    0.00    0.00    0.00    0.00    0.00   21.00
02:47:50 1      19.00    0.00   65.00    0.00    0.00    0.00    0.00    0.00   16.00
[...]

各列含义:
- CPU:逻辑CPU ID,all表示总结信息。
- %usr:用户态时间。
- %nice:以nice优先级运行的进程用户态时间。
- %sys:系统态时间(内核)。
- %iowait:I/O等待。
- %irq:硬件中断CPU用量。
- %soft:软件中断CPU用量。
- %steal:耗费在服务其他租户的时间。
- %guest:花在访客虚拟机的时间。
- %idle:空闲 。重要列有%usr%sys%idle,可显示每个CPU用量及用户态和内核态时间比例,还能找出“热”CPU 。

  • Solaris版本mpstat(1M) 一开始输出系统启动后的统计信息,然后是每隔一段时间的总结信息 。示例输出及各列含义:
$ mpstat 1
CPU minf mjf xcal intr ithr csw icsw migr smtx  srw syscl usr sys wt  idl
[...]
0  8243  0  288 3211 1265 1682 40  236 262  0  8214 47  19  0  34
1  43708 0  1480 2753 1115 1238 58  406 1967 0  26157 17 59  0  24
[...]

- **CPU**:逻辑CPU ID。
- **xcal**:CPU交叉调用。
- **intr**:中断。
- **ithr**:线程服务的中断(低级IPL)。
- **csw**:上下文切换(总数)。
- **icsw**:非自愿上下文切换。
- **migr**:线程迁移。
- **smtx**:在互斥锁上等待。
- **srw**:在读/写锁上等待。
- **syscl**:系统调用。
- **usr**:用户态时间。
- **sys**:系统态时间(内核)。
- **wt**:等待I/O(已废弃,永远为0)。
- **idl**:空闲 。关键列有`xcal`(检查是否因频率过高消耗CPU资源 )、`smtx`(检查是否因频率过高消耗CPU资源及锁竞争情况 )、`usr`、`sys`和`idl`(刻画每个CPU用量及用户态与内核态比例 ) 。

6.6.4 sar工具

系统活动报告器sar(1) ,可用于观察当前活动,还能归档和报告历史统计信息 。

  • Linux版本选项
  • -P ALL:与mpstat(1)-P ALL选项功能相同,可获取每个CPU的统计信息。
  • -u:与mpstat(1)默认输出类似,仅包含系统范围的平均值。
  • -q:包含运行队列长度列runq-sz(等待数加上运行数,与vmstatr列相同)和平均负载 。
  • Solaris版本选项
  • -u:展示系统范围内%usr%sys%wio(零)和%idl的平均值。
  • -q:包含运行队列长度列runq-sz(仅包括等待数),以及运行队列中有线程等待的百分比时间%runocc(该值在0和1之间,不够准确 )。Solaris版本不提供单个CPU的统计信息 。
6.6.5 ps工具

进程状态命令ps(1) 可列出所有进程的详细信息,包括CPU用量统计信息 。

  • 操作风格及示例
  • 源于BSD风格:如ps aux ,其中a列出所有用户,u提供面向用户的扩展信息,x列出没有终端的进程 。输出示例如下:
$ ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0  23772  1948?        S    2012   0:00 init
root         2  0.0  0.0      0     0?        S    2012   0:00 [kthreadd]
root         3  0.0  0.0      0     0?        S    2012   0:26 [ksoftirqd/0]
[...]
web      11715 11.3  0.0 632700 11540 pts/0   Sl   01:36  0:27 node indexer.js
web      11721 96.5  0.1 638116 52108 pts/1  R+   01:37  3:33 node proxy.js
[...]

- **源于SVR4风格**:如`ps -ef` ,`-e`列出所有进程,`-f`显示完整信息 。输出示例如下:

$ ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 Nov13?        00:00:04 /sbin/init
root         2     0  0 Nov13?        00:00:00 [kthreadd]
root         3     2  0 Nov13?        00:00:00 [ksoftirqd/0]
[...]
  • CPU用量相关列
  • TIME列:显示进程自创建起消耗的CPU总时间(用户态 + 系统态 ),格式为“小时:分钟:秒” 。
  • %CPU列:在Linux上,显示前一秒内所有CPU上的CPU用量之和,单线程CPU型进程报告100% ,双线程报告200% ;在Solaris上,会根据CPU数量正规化,如单个CPU消耗型线程在八CPU系统上显示为12.5% ,该指标显示最近的CPU用量,采用类似平均负载的衰退平均数 。此外,ps(1)还有-o等选项用于定制输出和显示列 。
6.6.6 top工具

top(1) 由William LeFevre于1984年为BSD开发,灵感源自VMS命令MONITOR PROCESS/TOPCPU,该命令可显示最消耗CPU的任务及CPU消耗百分比,还有ASCII字符的条形直方图(多为整数数据 ) 。

  • 基本使用top命令以一定间隔刷新屏幕展示系统信息 。在Linux上示例输出如下:
$ top
top - 01:38:11 up 63 days, 1:17, 2 users, load average: 1.57, 1.81, 1.77
Tasks: 256 total,  2 running, 254 sleeping,  0 stopped,  0 zombie
%Cpu(s):  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  49548744k total, 16746572k used, 32802172k free,  182900k buffers
KiB Swap: 100663292k total,        0k used, 100663292k free, 1492524k cachedPID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
11721 web       20   0  623m  50m 4984 R  93  0.1   0:59.50 node
11715 web       20   0  619m  20m 4916 S  25  0.0   0:07.52 node
10 root      20   0      0     0     0 S   0  0.0   0:24:52.56 ksoftirqd/2
11 root      20   0      0     0     0 S   0  0.0   0:00:00.00 kworker/2:0
11724 admin     20   0  19412 1444  960 R   0  0.0   0:00.07 top
1 root      20   0  23772  1948 1296 S   0  0.0   0:04.35 init

顶部是系统范围统计信息,包括平均负载和CPU状态(%us%sy%ni%id%wa%hi%si%st ,与mpstat(1)打印的相同,是所有CPU的平均值 ) 。下方是进程/任务列表,默认按CPU用量排序 。CPU用量用TIME+%CPU列显示,TIME+列精度达百分之一秒,如“1:36.53”代表CPU已用总时间为1分36.53秒,有些版本还提供“累计时间”模式 。

  • CPU用量统计模式:在Linux上,%CPU列默认未按CPU数量正规化,即“Irix模式” 。可切换到“Solaris模式”,将CPU用量除以CPU数量,如16CPU服务器上的双线程热点进程在该模式下CPU占用百分比报告为12.5% 。
  • 自身资源占用及改进工具top(1)自身CPU用量可能较高,因使用open()read()close()等系统调用,遍历/proc多进程项目时开销大 。prstat(1M)工具也存在类似问题 。且top(1)/proc快照,会错过短命进程,Linux上的atop(1)使用进程核算技术捕捉短命进程并显示 。
6.6.7 prstat工具

prstat(1) 命令在“基于Solaris系统上的top”里有介绍 。

  • 基本输出示例
$ prstatPID USERNAME   SIZE   RSS STATE  PRI NICE   TIME     CPU PROCESS/NLWP
214929 root     321M   304M sleep    59      0  2:57:41  0.8% node/smp/594
20721  root     345M   343M sleep    59      0  2:49:53  0.8% node/5
15354  root     172M   156M cpu3     1      0  0:31:42  0.7% node/4
21738  root     179M   143M sleep    59      0  2:37:48  0.7% node/s
21739  root     179M   143M sleep    59      0  2:37:48  0.7% node/s
23186  root     172M   149M sleep    59      0  0:10:56  0.6% node/4
16513  root     179M   148M cpu13    1      0  2:36:43  0.6% node/4
19634  root     139M   170M sleep    59      0  0:29:36  0.5% node/4
23214  root     139M   170M sleep    59      0  0:29:36  0.5% node/4
16299  root     139M   177M sleep    59      0  1:56:10  0.4% node/s
37088  root    1069M  1056M sleep    59      0 38:31:19  0.3% gem-system-x86/4
Total: 390 processes, 1758 lwps, load averages: 3.89, 3.99, 4.31

底部是系统信息总结,CPU展示最近的CPU用量(与Solaris上的top(1)是同一指标 ),TIME显示消耗时间 。prstat(1M) 通过在文件描述符打开时用pread()读取/proc状态,相比top(1)(反复用open()read()close() )消耗更少CPU资源 。

  • 线程微状态统计输出示例:使用-mL选项可按每个线程打印(每个LWP )并持续输出(非刷新屏幕 )。
$ prstat -mLcPID USERNAME   USR   SYS  TRP  TFL  DFL  LCK  SLP  LAT  PROCESS/LWPID
30210 root       8.8  1.2  0.0  0.0  0.0  0.0  90.0  0.0 node/i
42370 root      11.2  0.0  0.0  0.0  0.0  0.0  87.1  1.7 205 23 2K node/i
42328 root      11.1  0.9  0.0  0.0  0.0  0.0  87.0  1.0 205 25 2K node/i
42808 root      11.9  0.0  0.0  0.0  0.0  0.0  87.1  1.0 201 24 2K node/i
56318 root       6.8  1.4  0.0  0.0  0.0  0.0  92.0  0.1 156 23 1K node/i
55032 root       6.8  1.3  0.0  0.0  0.0  0.0  92.1  0.1 156 21 1K node/i
54445 root       6.7  1.3  0.0  0.0  0.0  0.0  92.1  0.1 156 24 1K node/i
54222 root       6.8  1.3  0.0  0.0  0.0  0.0  92.0  0.0 156 20 1K node/i
21222 103       4.1  1.0  0.0  0.0  0.0  0.0  94.0  0.0 43  0 1K beam.smp/585
21217 103       4.1  1.0  0.0  0.0  0.0  0.0  94.0  0.0 43  0 1K beam.smp/585
21222 103       4.7  1.1  0.0  0.0  0.0  0.0  93.7  0.5 985  beam.smp/590
Total: 390 processes, 1758 lwps, load averages: 3.92, 3.99, 4.31

高亮八列显示花在每个微状态的时间(总和为100% ),各列含义:
- USR:用户态时间。
- SYS:系统态时间(内核)。
- TRP:系统陷阱。
- TFL:文本段缺页(可执行数据段缺页)。
- DFL:数据段缺页。
- LCK:花在等待用户级锁的时间。
- SLP:花在睡眠的时间(包括在I/O上阻塞 )。
- LAT:调度器延时(分发器队列延时 )。
根据线程时间分解,下一步调查方向:
- USR:剖析用户态CPU用量。
- SYS:检查使用的系统调用并剖析内核态CPU用量。
- SLP:依据睡眠事件,跟踪系统调用或代码路径获取更多细节。
- LAT:检查系统范围内的CPU用量及强制的CPU限制/配额 。许多工作可通过DTrace完成 。

6.6.8 pidstat工具

Linux上的pidstat(1) 工具按进程或线程打印CPU用量,包括用户态和系统态时间分解 。默认仅循环输出活动进程信息 。示例如下:

$ pidstat 1
Linux 2.6.35-32-server (dev7)  11/12/12  _x86_64_    (16 CPU)
22:24:42       PID    user   system  guest  %CPU   CPU  Command
22:24:43      7815  97.03    2.97    0.00 100.00   11  gzip
22:24:43      7815  97.03    2.97    0.00 100.00   11  gzip
23:24:43       448  0.00    1.00    0.00   1.00    0  kjournald
22:24:44      7814  0.00    2.00    0.00   2.00    3  tar
22:24:44      7816  0.00    2.00    0.00   2.00    2  pidstat
22:24:44      7816  0.00    2.00    0.00   2.00    2  pidstat
[...]

该示例捕捉到系统备份,gzip(1)内核态时间高(因其压缩代码为CPU密集型 ),tar(1)从文件系统读取文件,在内核中消耗更多时间 。选项-p ALL可打印所有进程(包括空闲进程 ),选项-t打印每个线程统计信息 ,其他选项在本书其他章节介绍 。

6.6.9 prstat工具使用及性能分析

prstat(1) 命令可用于分析系统性能,在基于Solaris系统中有重要应用 。

  • 基本进程信息查看:执行prstat命令,可展示进程相关信息,示例输出如下:
$ prstatPID USERNAME   SIZE   RSS STATE  PRI NICE   TIME     CPU PROCESS/NLWP
214929 root     321M   304M sleep    59      0  2:57:41  0.8% node/smp/594
20721  root     345M   343M sleep    59      0  2:49:53  0.8% node/5
15354  root     172M   156M cpu3     1      0  0:31:42  0.7% node/4
[...]
Total: 390 processes, 1758 lwps, load averages: 3.89, 3.99, 4.31

底部系统信息总结中,CPU展示进程最近的CPU用量(与Solaris上的top(1)是同一指标 ),TIME显示进程消耗时间 。通过这些信息可初步了解各进程资源占用及运行时长情况 。且prstat(1M) 相比top(1)(反复使用open()read()close() ),通过在文件描述符打开时用pread()读取/proc状态,消耗更少CPU资源 。

  • 线程微状态统计分析:使用-mL选项可按每个线程打印(每个LWP )并持续输出(非刷新屏幕 ),示例如下:
$ prstat -mLcPID USERNAME   USR   SYS  TRP  TFL  DFL  LCK  SLP  LAT  PROCESS/LWPID
30210 root       8.8  1.2  0.0  0.0  0.0  0.0  90.0  0.0 node/i
42370 root      11.2  0.0  0.0  0.0  0.0  0.0  87.1  1.7 205 23 2K node/i
42328 root      11.1  0.9  0.0  0.0  0.0  0.0  87.0  1.0 205 25 2K node/i
[...]
Total: 390 processes, 1758 lwps, load averages: 3.92, 3.99, 4.31

高亮显示的八列展示了花在每个微状态的时间(总和为100% ),各列含义如下:
- USR:用户态时间,用于剖析用户态CPU用量,了解用户程序代码执行消耗的CPU时间 。
- SYS:系统态时间(内核),可检查进程使用的系统调用并剖析内核态CPU用量,分析内核为进程提供服务所花费的时间 。
- TRP:系统陷阱,反映因系统陷阱导致的CPU资源消耗情况 。
- TFL:文本段缺页(可执行数据段缺页),体现进程在处理文本段缺页时的CPU开销 。
- DFL:数据段缺页,展示进程处理数据段缺页所占用的CPU时间 。
- LCK:花在等待用户级锁的时间,帮助分析因锁竞争导致的CPU等待时间 。
- SLP:花在睡眠的时间(包括在I/O上阻塞 ),依据睡眠事件,可跟踪系统调用或代码路径获取更多细节,判断是否因I/O操作等导致进程长时间睡眠占用CPU资源 。
- LAT:调度器延时(分发器队列延时 ),用于检查系统范围内的CPU用量及强制的CPU限制/配额 ,分析调度器对进程调度的延迟情况对CPU性能的影响 。通过对这些微状态时间的分析,能深入了解线程在CPU上的运行行为,定位性能瓶颈 。

6.6.10 DTrace工具在CPU分析中的应用

DTrace可用于剖析用户级和内核级代码的CPU用量,还能跟踪函数执行、CPU交叉调用、中断和内核调度器等,支持负载特征分析、剖析、下钻分析和延时分析等 。在Solaris和Linux系统上均可使用(部分功能有差异 ),入门介绍见第4章。

  • 内核剖析:通过dtrace -n 'profile-997 /arg0/ { @[stack()] = count(); }'等单行命令,以997Hz频率采样内核栈跟踪信息,可了解内核具体操作。还可通过调整命令,如仅输出最频繁的10个、每个栈只输出5帧、采样在CPU上运行的函数或模块等,简化输出并聚焦关键信息 。
  • 用户剖析:以类似内核剖析方法,通过dtrace -n 'profile-97 /arg1 && execname == "mysqld"/ { @[ustack()] = count(); }'等命令,可剖析用户态时间消耗。还可按特定条件(如指定PID、进程名等 )采样用户栈,或调整输出格式(如仅输出前10个、仅输出5个栈帧、仅输出函数名或模块名等 ) 。
  • 函数跟踪:利用动态跟踪(如fbt provider ),通过dtrace -n 'fbt::zio_checksum_generate:entry { self->v = timestamp; } fbt::zio_checksum_generate:return /self->v/ { @["ns"] = quantize(timestamp - self->v); self->v = 0; }'等命令,可测量函数的CPU时间及分布 。不过通过fbt或pid provider进行动态跟踪因函数随版本变化可能不稳定,也有静态provider提供稳定接口 。
  • CPU交叉调用跟踪:使用dtrace -n 'sysinfo::xcalls { @[stack()] = count(); }'等命令,可打印CPU交叉调用及导致这些调用的代码路径,过多交叉调用会因CPU消耗影响性能 。
  • 中断跟踪:基于Solaris系统的intrstat(1M)命令(基于DTrace )可总结中断CPU用量,显示中断次数及每个驱动程序在每个CPU上的CPU时间百分比。若intrstat(1M)不可用(如Linux ),可使用动态函数跟踪检查中断事件 。
  • 调度器跟踪:调度器provider(sched )提供对内核CPU调度器的跟踪操作,包含on - cpu(当前线程开始在CPU上执行 )、off - cpu(当前线程马上退出CPU执行 )等探测器 。通过dtrace -n 'sched::on-cpu /execname == "sshd"/ { self->ts = timestamp; } sched::off-cpu /self->ts/ { @["ns"] = quantize(timestamp - self->ts); self->ts = 0; }'等命令,可跟踪特定进程(如“sshd” )在CPU上的运行时间 。
6.6.11 SystemTap工具

在Linux系统中,SystemTap可用于跟踪调度器时间 。若需将之前的DTrace脚本进行转换,可参考第4章的4.4节和附录E获取相关帮助信息 。

6.6.12 perf工具介绍及使用

perf(1) 原名Linux性能计数器(PCL),现称Linux性能事件(LPE) ,是一套用于剖析和跟踪的工具,通过子命令实现不同功能 。部分子命令及描述如下:

命令

描述

annotate

读取perf.data(由perf record创建)并显示注释过的代码

diff

读取两个perf.data文件并显示两份剖析信息之间的差异

evlist

列出一个perf.data文件里的事件名称

inject

过滤以加强事件流,在其中加入额外的信息

kmem

跟踪/测量内核内存(slab)属性的工具

kvm

跟踪/测量kvm客户机操作系统的工具

list

列出所有的符号事件类型

lock

分析锁事件

probe

定义新的动态跟踪点

record

运行一个命令,并把剖析信息记录在perf.data

report

读取perf.data(由perf record创建)并显示剖析信息

sched

跟踪/测量调度器属性(延时)的工具

script

读取perf.data(由perf record创建)并显示跟踪输出

stat

运行一个命令并收集性能计数器统计信息

timechart

可视化某一个负载期间系统总体性能的工具

top

系统剖析工具

系统剖析:使用perf record -a -g -F 997 sleep 10 以997Hz频率对调用栈采样10s(-a 对所有CPU采样,-g 记录调用栈 ),生成perf.data文件,再用perf report --stdio查看 。输出按取样计数排序,显示CPU时间去向,如示例中72.98%时间花在空闲线程,9.43%时间花在dd进程 。

进程剖析:执行perf record -g command 可对单个进程剖析,创建perf.data文件,查看报告时需调试信息文件进行符号转译 。

调度器延时perf sched record sleep 10 记录调度器统计信息,生成perf.data文件,perf sched latency 可报告跟踪时期平均和最大的调度器延时 。但此类跟踪因调度器事件频繁,会产生CPU和存储开销 。

stat命令perf stat gzip file1 基于CPC为CPU周期行为提供概要总结,统计信息包括周期和指令计数、IPC(CPI倒数 )等 。还可通过perf list 列出可检查的计数器,如cpu-cyclesinstructions 等硬件事件,以及LLC-load-misses(末级缓存负载未命中 )等缓存相关事件 。可使用-e选项指定事件,如perf stat -e instructions,cycles gzip file1

软件跟踪perf record -e 配合软件性能测量点(由perf list 列出,如context-switchescpu-migrations 等软件事件,sched:sched_switch 等跟踪点事件 ),可跟踪内核调度器活动 。如使用上下文切换软件事件跟踪应用程序离开CPU时的情况,收集调用栈信息 。还可结合动态跟踪点跟踪内核调度器函数,获取类似DTrace的数据,但可能需更多事后处理 。更多信息可参考其手册及Linux内核源代码tools/perf/Documentation下的相关文档 。

6.6.13 cpustat工具(Solaris系统)

在基于Solaris的系统中,cpustat(1M) 用于全系统CPU性能检查,cputrack(1M) 针对进程分析 ,它们将CPC(性能计数器 )称为性能测量计数器(PICs ) 。通过以下命令测量CPI(需计算周期数和指令数 ):

  • cpustat -t cpu -e PAPI_tot_cyc,PAPI_tot_ins,sys 1cpustat(1M) 为每个CPU生成一行输出,可进一步处理(如用awk计算CPI ) 。通过设置标志位使用sys令牌计算用户态和内核态周期 。
  • cpustat -to cpu_clk_unhalted.thread_p,inst_retired.any_p,sys 1 ,通过平台相关事件名测量计数器 。运行cpustat -h 可获取处理器支持的计数器列表,输出常引用供应商处理器手册 。系统同时只能运行一个cpustat(1M) 实例,因内核不支持多路复用 。
6.6.14 其他性能分析工具
  • Linux系统
  • oprofile:由John Levon开发的最初CPU剖析工具 。
  • htop:含CPU用量ASCII柱状图,交互模式比top(1) 更强大 。
  • atop:提供更多系统级统计信息,利用进程核算统计捕捉短命进程 。
  • /proc/cpuinfo:可获取处理器详细信息,如时钟频率、特性标志位 。
  • getdelays.c:用于延时核算观察,展示每个进程的CPU调度器延时,第4章有演示 。
  • valgrind:内存调试和剖析工具组,callgrind 可跟踪函数调用并生成调用图(配合kcachegrind 可视化 ),cachegrind 可分析程序硬件缓存用量 。
  • Solaris系统
  • lockstat/plockstat:用于锁分析,能分析自旋锁和自适应互斥量上的CPU消耗(详见第5章 ) 。
  • psrinfo:可查看处理器状态和信息(-vp 选项 )。
  • fmadm faulty:检查CPU是否因可更正ECC错误增多进入预测故障模式,也可参考fmstat(1M)
  • isainfo -x:列出处理器特性标志位 。
  • pginfo、pgstat:提供处理器组统计信息,展示CPU拓扑及资源共享情况 。
  • lgrpinfo:提供本地性组统计信息,检查使用中的lgrps(需处理器和操作系统支持 ) 。此外,还有支持Solaris和Linux的Oracle Solaris Studio等复杂CPU性能分析产品 。

6.7 实验

本节介绍主动测试CPU性能的工具,背景信息参考6.5.11节 。使用时建议让mpstat(1)持续运行,用于确认CPU用量和并发度。

6.7.1 Ad Hoc
  • 虽非测量工具,但可作为已知负载确认观察工具是否正常工作。通过Bourne脚本程序(while ;; do ;; done & )在后台执行无限循环,创建单线程CPU密集型负载,不需要时需终止该进程。
6.7.2 SysBench
  • SysBench系统基准测试套件含计算质数的简单CPU基准测试工具。示例命令 sysbench --num -threads=8 --test=cpu --cpu -max -prime=100000 run ,执行8个线程,最多计算100000个质数,运行时间30.4s 。可用于与其他系统或配置对比(需满足诸多假定,如构建软件使用相同编译器选项等,详见第12章)。

6.8 调优

对于CPU,排除不必要工作是有效的调优手段,6.5节和6.6节介绍过相关分析辨识方法。此外,还有优先级调优和CPU绑定等调优方法。调优选项和设置取决于处理器类型、操作系统版本和任务需求。

6.8.1 编译器选项

编译器及其优化代码选项对CPU性能影响大,常见选项包括编译为64位程序、优化级别等,编译器优化在第5章有讨论。

6.8.2 调度优先级和调度类
  • Linuxnice(1)命令调整进程优先级,正nice值调低优先级,负值调高(负值仅超级用户可设,范围 -20 ~ +19 ,如nice -n 19 command );renice(1)可更改已运行进程优先级;chrt(1)命令可显示并设置优先级和调度策略,也可通过setpriority()系统调用、 sched_setscheduler()设置。
  • Solarispriocntl(1)命令可直接设置调度类和优先级,如priocntl -e -c RT -p 10 -i pid PID ,使目标ID进程以优先级10在实时调度类下运行,操作需谨慎,避免系统锁死。
6.8.3 调度器选项
  • Linux:内核提供控制调度器行为的可调参数,部分配置选项如CONFIG_CGROUP_SCHED(允许任务编组分配CPU时间)等(源于3.2.6版内核,附Fedora 16默认值 ),有些内核在/proc/sys/sched中还有额外可调参数。
  • Solaris:部分内核可调参数可修改调度器行为,具体参考对应操作系统版本文档(如Solaris Tunable Parameters Reference Manual ),使用需谨慎,调优可能受公司或供应商政策限制。

Linux 内核提供一些控制调度器行为的可调参数,多数情况下无需调整。在 Linux 系统(基于 3.2.6 版内核,以 Fedora 16 为例 )中,可设置以下

选项

默认值

描述

CONFIG_CGROUP_SCHED

y

允许任务编组,以组为单位分配 CPU 时间

CONFIG_FAIR_GROUP_SCHED

y

允许编组 CFS 任务

CONFIG_RT_GROUP_SCHED

y

允许编组实时任务

CONFIG_SCHED_AUTOGROUP

y

自动识别并创建任务组(例如,构建任务 )

CONFIG_SCHED_SMT

y

超线程支持

CONFIG_SCHED_MC

y

多核支持

CONFIG_HZ

1000

设置内核时钟频率(时钟中断 )

CONFIG_NO_HZ

y

无 tick 内核行为

CONFIG_SCHED_HRTICK

y

使用高精度定时器

CONFIG_PREEMPT

n

全内核抢占(除了自旋锁区域和中断 )

CONFIG_PREEMPT_NONE

n

无抢占

CONFIG_PREEMPT_VOLUNTARY

y

在自愿内核代码点进行抢占

此外,有些 Linux 内核在 /proc/sys/sched 中还提供额外可调参数。

Solaris 调度器可调参数

在基于 Solaris 系统中,可通过以下内核可调参数修改调度器行为:

参数

默认值

描述

rechoose_interval

3

CPU 关联持续时间(时钟 tick )

nosteal_nsec

100 000

如果线程在最近时间(纳秒级 )运行过则避免线程偷取(空闲 CPU 找活干 )

hires_tick

0

设为 1 代表把内核时钟频率设置到 1000Hz,而不是默认的 100Hz

Solaris 调度器类调优

基于 Solaris 的系统可通过 dispadmin(1) 命令修改调度器类使用的时间片和优先级。以分时调度器类(TS)为例 ,使用 dispadmin -c TS -g -r 1000 命令可打印其可调参数(分发器表 ),输出包含:

  • ts_quantum:时间片(单位为毫秒,精度通过 -r 1000 设置 )。
  • ts_tqexp:线程当前时间片过期之后的新优先级(优先级削减 )。
  • ts_slpret:在线程睡眠(I/O)之后醒来的新优先级(优先级提升 )。
  • ts_maxwait:在线程被提升至 ts_lwait 一栏的优先级前等待 CPU 的最大秒数。
  • PRIORITY LEVEL:优先级值。

这些配置可写入文件修改后,由 dispadmin(1M) 重新载入,可先借助 DTrace 度量优先级竞争和调度器延时 。

6.8.4 进程绑定

进程可绑定到一个或多个CPU上,借此提升缓存温度与内存本地性,进而提高性能。

  • Linux系统:通过taskset(1)命令实现。能利用CPU掩码或范围设置CPU关联性。示例中,$ taskset -pc 7 - 10 10790将PID为10790的进程限定在CPU 7到CPU 10间运行 。
  • Solaris系统:借助pbind(1)实现。如$ pbind -b 10 11901将PID为11901的进程限定在CPU 10上运行,该系统不能指定多个CPU,若需类似功能,需使用独占CPU组。
6.8.5 独占CPU组
  • Linux系统:提供CPU组功能,可编组CPU并为其分配进程,与进程绑定类似可提升性能,且独占CPU组能禁止其他进程使用,不过会减少系统其他部分可用CPU数量。示例命令创建名为“prodset”的独占组,指定CPU 7 - 10 ,并将PID 1159的进程分配其中 。
  • Solaris系统:可通过psrset(1M)命令创建独占CPU组。
6.8.6 资源控制

现代操作系统除实现进程与CPU关联外,还对CPU用量分配进行细粒度资源控制。

  • Solaris系统:将进程或进程组的资源控制机制(Solaris 9引入)称为项目。利用公平份额调度器和份额灵活控制CPU用量,还可对CPU总使用率百分比设限。
  • Linux系统:通过控制组(cgroups)控制进程或进程组资源用量。使用份额控制CPU用量,CFS调度器可设置每段时间内分配CPU微秒数周期的固定上限(CPU带宽,2012年3.2版引入 )。第11章有管理虚拟化操作系统租户CPU用量相关用例。