《性能之巅》读书笔记

前言

最近两周的工作有一部分是性能优化,包括排查性能问题,部分架构优化,热点函数优化等。

感觉自己在工作中对还是对性能这块蛮有兴趣的,原因如下:

  1. 因为这块的工作能有结实的产出。我现在负责的几个模块是前人留下的,或多或少总能发现性能的问题或者工程上不合理的处理并解决。
  2. 性能问题是个手艺活,在一个复杂的环境,复杂的项目里,如何快速的定位?这里可以整理成相关sop。
  3. 相关问题的查找和分析需要用到一些和计算机四大件相关的知识,让我觉得学有所用,有一种丰收的喜悦。

当然,一项技能——便定义为:能够快速排查系统性能问题并解决的问题。我认为最首要的是,定位到问题点,每个尝试的步骤都可以算是个点,一次完整的发现问题->解决问题的实践可以算是一条线, 连点成线,以线织面是自下而上地通过实践去学习技能。但是我们同样需要理论的指导,那么什么是性能相关的理论指导?站在巨人的肩膀上是一个好的选择—— Brendan Gregg,这位大师的相关视频以及书籍《性能之巅》能够给我们很好的指导。

找到了第一版的中文版和第二版的英文版。

  • 第1版:适合初学者构建性能分析基础框架,或仅关注传统物理机/虚拟机环境29。
  • 第2版:
    • 需解决云、容器、微服务环境性能问题;
    • 掌握eBPF等前沿动态追踪技术;
    • 从事大规模分布式系统运维(如SRE、云架构师)

全书内容较多,我只截取个人认为有帮助的部分。

笔记

放在开头的是每种操作的耗时, 我想每个程序员应该都要有一个概念

在这里贴上 B站一位UP做的关于内存知识分享的地址,其中P2的1分30s左右有引用Google给出的类似总结(2004年,时效性不足):

[原创] 每个程序员都应该知道的内存知识 (带你走读Ulrich Drepper的经典论文)_哔哩哔哩_bilibili

印象里CMU15445的课件里也有整理过。

第三章 操作系统

进程环境

什么是进程环境?

包括进程地址空间内的数据和内核的上下文。

内核上下⽂包含了各种进程的属性和统计信息:它的进程 ID(PID)、所有者的⽤户 ID(UID),以及各种类型的 时间。这些通常⽤ ps(1) 命令来检查。还有⼀套⽂件描述符,指向的是打开的⽂件,这些⽂件为线程之间所共享 (通常来说)。 ⽤户地址空间包括进程的各种内存段:可执⾏⽂件、库和堆。相关信息存放在/proc下。

系统调用

  • ioctl( ):这个系统调⽤⽤于向内核请求各种各样的操作,特别是针对系统管理⼯具。
  • mmap( ):这个系统调⽤通常⽤来把可执⾏⽂件和库以及内存映射⽂件映射到进程的地址空间。有时候会替代 基于 brk( ) 的 malloc( ) 对进程的⼯作内存做分配,以减少系统调⽤的频率,提升性能(并不总是这样,内存 映射管理会做⼀些权衡)。
  • brk( ):这个系统调⽤⽤于延伸堆的指针,该指针定义了进程⼯作内存的⼤⼩。这个操作通常是由系统内存分 配库执⾏的,当调⽤ malloc( )(内存分配)不能满⾜堆内现有空间时发⽣。

要注意的是,如果是数据密集型的进程,应尽量避免进行系统调用

用户态和内核态切换开销大

  • 用户程序运行在用户态(权限低);
  • 系统调用需要切换到内核态(权限高);
  • 这种从用户态切换到内核态再切回用户态的过程本身就是昂贵的(涉及上下文保存/恢复、中断控制、权限切换等)。

缓存失效(Cache Miss)

切换到内核态可能会导致 CPU 缓存失效,破坏原本的数据局部性。

  • 用户态中的缓存数据在切到内核态时不一定会保留;
  • 导致重新加载缓存,增加额外时间。

上下文切换可能引发调度

一些系统调用(比如 read() 阻塞)可能会引发上下文切换到其他线程或进程,这会带来更多开销。

第四章 观测工具

重要的部分,当系统出问题的时候我们如何排查?

计数器

内核维护了各种统计数据,称为计数器,⽤于对事件计数。通常计数器实现为⽆符号的整型数,发⽣事件时递增。 例如,有⽹络包接收的计数器,有磁盘 I/O 发⽣的计数器,也有系统调⽤执⾏的计数器。计数器的使⽤可以认为是 “零开销”的,因为它们默认就是开启的,⽽且始终由内核维护。

系统级别

  • vmstat:虚拟内存和物理内存的统计,系统级别。
  • mpstat:每个 CPU 的使⽤情况
  • iostat:每个磁盘 I/O 的使⽤情况,由块设备接⼝报告
  • netstat:⽹络接⼝的统计,TCP/IP 栈的统计,以及每个连接的⼀些统计信息。
  • sar:各种各样的统计,能归档历史数据。

进程级别

  • ps:进程状态,显示进程的各种统计信息,包括内存和 CPU 的使⽤
  • top:按⼀个统计数据(如 CPU 使⽤)排序,显示排名⾼的进程。基于 Solaris 的系统对应的⼯具是 prstat(1M)
  • pmap:将进程的内存段和使⽤统计⼀起列出。

跟踪

系统级别

  • tcpdump:⽹络包跟踪(⽤ libpcap 库)
  • snoop:为基于 Solaris 的系统打造的⽹络包跟踪⼯具
  • blktrace:块 I/O 跟踪(Linux)
  • iosnoop:块 I/O 跟踪(基于 DTrace)
  • execsnoop:跟踪新进程(基于 DTrace)
  • dtruss:系统级别的系统调⽤缓冲跟踪(基于 DTrace)
  • DTrace:跟踪内核的内部活动和所有资源的使⽤情况(不仅仅是⽹络和块 I/O),⽀持静态和动态的跟踪。
  • SystemTap:跟踪内核的内部活动和所有资源的使⽤情况,⽀持静态和动态的跟踪。 perf:Linux 性能事件,跟踪静态和动态的探针。

进程级别

  • strace:基于 Linux 系统的系统调⽤跟踪
  • truss:基于 Solaris 系统的系统调⽤跟踪
  • gdb:源代码级别的调试器,⼴泛应⽤于 Linux 系统
  • mdb:Solaris 系统的⼀个具有可扩展性的调试器

排查方法

我们缺乏的从来不是工具,我们缺乏的是排查问题的思路,是否有相关的介绍?

让我们看看Brendan Gregg的讲座 【Linux专题介绍】性能工具介绍(大牛Brendan Gregg)_哔哩哔哩_bilibili

我们也可以看本书的第二章 方法 中的USE方法,检查系统的各个指标:

USE 方法(utilization、saturation、errors)应用于性能研究,用来 识别系统瓶颈[Gregg 13]。一言以蔽之,就是: 对 于所有的资源,查看它的使用率、饱和度和错误。

观测来源

系统性能统计的主要来源是:/proc、/sys 和 kstat。后⾯还会介绍延时核算和微状态核算,以及其他的⼀些来源。

/proc 由内核动态创建,不需要任何存储设备(在内存中运⾏)。多数⽂件是只读的,为观测⼯具提供统计数据。 ⼀部分⽂件是可写的,⽤于控制进程和内核的⾏为。 Linux 中/proc 的⽂件系统类型是“proc”,在/proc 下有各种进程统计的⽂件。

进程性能观测相关的⽂件如下:

  • limits:实际的资源限制。
  • maps:映射的内存区域。
  • sched:CPU 调度器的各种统计。
  • schedstat:CPU 运⾏时间、延时和时间分⽚。
  • smaps:映射内存区域的使⽤统计。
  • stat:进程状态和统计,包括总的 CPU 和内存的使⽤情况。
  • statm:以⻚为单位的内存使⽤总结。
  • status:stat 和 statm 的信息,⽤户可读。
  • task:每个任务的统计⽬录。

系统性能观测相关的系统级别的⽂件如下:

  • cpuinfo:物理处理器信息,包含所有虚拟 CPU、型号、时钟频率和缓存⼤⼩
  • diskstats:对于所有磁盘设备的磁盘 I/O 统计
  • interrupts:每个 CPU 的中断计数器
  • loadavg:负载平均值
  • meminfo:系统内存使⽤明细
  • net/dev:⽹络接⼝统计
  • net/tcp:活跃的 TCP 套接字信息
  • schedstat:系统级别的 CPU 调度器统计
  • self:关联当前进程 ID 路径的符号链接,为了使⽤⽅便
  • slabinfo:内核 slab 分配器缓存统计
  • stat:内核和系统资源的统计,CPU、磁盘、分⻚、交换区、进程
  • zoneinfo:内存区信息。

其他的观测源

  • CPU 性能计数器:可编程的硬件寄存器,提供低层级的性能信息,包括 CPU 周期计数、指令计数、停滞周 期,等等。在 Linux 上是通过 perf_events 接⼝,或者系统调⽤ perf_event_open(),或者 perf(1) 这样的⼯ 具来访问这些计数器的。
  • 进程级别跟踪:跟踪的是⽤户级别软件事件,如系统调⽤和函数调⽤。⼀般执⾏的代价较⾼,会拖慢跟踪的⽬ 标。在 Linux 上有系统调⽤ ptrace() 来控制进程跟踪,strace(1) ⽤它来跟踪系统调⽤。Linux 还有 uprobes 来做⽤户级别的动态跟踪
  • 内核跟踪:在 Linux 中,tracepoints 提供静态的内核探针(原先叫做内核标记,kernel markers), kprobes 提供动态探针。⼯具 ftrace、perf(1)、DTrace 和 SystemTap 都⽤到了这两项。
  • ⽹络嗅探:⽹络嗅探提供了⼀种从⽹络设备上抓包的⽅法,能对数据包和协议的性能做详细的调查。在 Linux 上,嗅探的功能是通过 libpcap 库和/proc/net/dev 提供的,命令⾏⼯具则有 tcpdump(8)。
  • 进程核算:⼯具 atop(1) ⽤进程核算能捕捉到短暂存活的进程并显示其信息,⽽⽤/proc 快照的办法很可能⽆ 法觉察到这件事。
  • 系统调⽤:⼀些可⽤的系统调⽤和库函数调⽤能提供某些性能指标。其中包括 getrusage(),这个函数调⽤是 为进程拿到⾃⼰资源的使⽤统计 ,包括⽤户时间、系统时间、错误、消息,以及上下⽂切换。
  • 更多:I/O 核算、blktrace、timer_stats、lockstat、debugfs。

第五章 应用程序分析

方法和分析

线程状态分析

线程状态分析的⽬的是分辨应⽤程序线程的时间⽤在了什么地⽅。如果时间⼤量地花在 CPU 上了,CPU 的剖析能 很快地解释这⼀点(稍后介绍)。许多性能问题都是这种情况,因此并没有必要花时间在其他状态的测量上。

六种状态

  • 执⾏:在 CPU 上。
  • 可运⾏:等待轮到上 CPU。
  • 匿名换⻚:可运⾏,但是因等待匿名换⻚⽽受阻。
  • 睡眠:等待包括⽹络、块设备和数据/⽂本⻚换⼊在内的 I/O。
  • 锁:等待获取同步锁(等待其他线程)。
  • 空闲:等待⼯作。

通过减少这些状态中的前五项的时间,会得到性能提升,同时也会增加空闲的时间。

  • 执⾏:检查执⾏的是⽤户态时间还是内核态时间,⽤剖析来做 CPU 资源消耗分析。剖析可以确定哪些代码路 径消耗 CPU 和消耗了多久,其中包括花费在⾃旋锁上的时间,
  • 可运⾏:在这个状态上耗时意味着应⽤程序需要更多的 CPU 资源。
  • 匿名换⻚:应⽤程序缺少可⽤的主存会引起换⻚和延时。
  • 睡眠:分析阻塞应⽤程序的资源。
  • 锁:识别锁和持有该锁的线程,确定线程持锁这么⻓时间的原因。

相关工具:

  • top -H
  • htop
    • F2进入设置,开启display 树状视图和线程名称
    • F10保存

相关的进程状态说明:

  • R running or runnable (on run queue) 正在执行或者可执行,此时进程位于执行队列中。
  • D uninterruptible sleep (usually I/O) 不可中断阻塞,通常为 IO 阻塞。
  • S interruptible sleep (waiting for an event to complete) 可中断阻塞,此时进程正在等待某个事件完成。
  • Z zombie (terminated but not reaped by its parent) 僵死,进程已经终止但是尚未被其父进程获取信息。
  • T stopped (either by a job control signal or because it is being traced) 结束,进程既可以被作业控制信号结束,也可能是正在被追踪。

其它状态:

  • I (Idle): 表示进程处于空闲状态,不执行任何操作。
  • P (Paging): 进程正在进行分页操作

cpu分析

剖析的⽬标是要判断应⽤程序是如何消耗 CPU 资源的。⼀个有效的技术是对 CPU 上的⽤户栈跟踪做采样并将采样 结果联系起来。栈跟踪告诉我们所选择的代码路径,这能够从⾼层和底层两⽅⾯揭示出应⽤程序消耗 CPU 的原因。

可使用强大的perf命令 以及 火焰图帮助我们分析cpu资源消耗。

系统调用分析

之前说到,在计算密集型的进程中尽量不要调用系统调用,那么有什么方法来跟踪它呢?

当然是强大的strace.

IO剖析

可使用iostat分析。

锁分析

对于多线程的应⽤程序,锁可能会成为阻碍并⾏化和扩展性的瓶颈。

锁的分析可以通过:

  • 检查竞争
  • 检查过⻓的持锁时间

第六章 CPU

关注的指标:

  1. 时钟频率:时钟是⼀个驱动所有处理器逻辑的数字信号。每个 CPU 指令都可能会花费⼀个或者多个时钟周期(称为 CPU 周 期)来执⾏。CPU 以⼀个特定的时钟频率执⾏,例如,⼀个 5GHz 的 CPU 每秒运⾏五⼗亿个时钟周期。更快的时 钟频率并不⼀定会提⾼性能——它取决于快速 CPU 周期⾥到底在做些什么。
    1. ⼀个指令包括以下步骤,每个都由 CPU 的⼀个叫作功能单元的组件处理:1. 指令预取 2. 指令解码 3. 执⾏ 4. 内存 访问 5. 寄存器写回
    2. 最后两步是可选的,取决于指令本身。许多指令仅仅操作寄存器,并不需要访问内存。
    3. 这⾥每⼀步都⾄少需要⼀个时钟周期来执⾏。内存访问经常是最慢的,因为它通常需要⼏⼗个时钟周期读或写主 存,在此期间指令执⾏陷⼊停滞(停滞期间的这些周期称为停滞周期)。这就是 CPU 缓存如此重要的原因:它可 以极⼤地降低内存访问需要的周期数
  2. 指令流水线
  3. 指令宽度:同⼀种类型的功能单元可以有好⼏个,这样每个时钟周期⾥就可以处理更多的指令。这种 CPU 架构被称为超标 量,通常和流⽔线⼀起使⽤以达到⾼指令吞吐量。指令宽度描述了同时处理的⽬标指令数量。现代处理器⼀般为宽 度 3 或者宽度 4,意味着它们可以在每个周期⾥最多完成 3~4 个指令
  4. CPI,IPC: 每指令周期数(CPI)是⼀个很重要的⾼级指标,⽤来描述 CPU 如何使⽤它的时钟周期,同时也可以⽤来理解 CPU 使⽤率的本质。这个指标也可以被表示为每周期指令数(instructions per cycle,IPC),即 CPI 的倒数。
  5. 使用率:CPU 使⽤率通过测量⼀段时间内 CPU 实例忙于执⾏⼯作的时间⽐例获得,以百分⽐表示。
  6. 用户时间/内核时间
  7. 饱和度:⼀个 100%使⽤率的 CPU 被称为是饱和的,线程在这种情况下会碰上调度器延时,因为它们需要等待才能在 CPU 上运⾏,降低了总体性能。这个延时是线程花在等待 CPU 运⾏队列或者其他管理线程的数据结构上的时间。
  8. 抢占

CPU缓存

注意缓存有分 指令缓存和数据缓存。

缓存行

cache line是内存读取数据的单位,x86_64架构的cpu上一个cache line大学为64Byte。

缓存一致性

内存可能会同时被缓存在不同处理器的多个 CPU ⾥。当⼀个 CPU 修改了内存,所有的缓存需要知道它们的缓存拷 ⻉已经失效(脏了),应该被丢弃,这样后续所有的读才会取到新修改的拷⻉。这个过程叫做缓存⼀致性,确保了 CPU 永 远访问正确的内存状态。这也是设计可扩展多处理器系统⾥最⼤的挑战之⼀,因为内存会被频繁修改。

MMU

MMU 负责虚拟地址到物理地址的转换。图 6.8 展示了⼀个普通的 MMU,附有 CPU 缓存类型。这个 MMU 通过⼀ 个芯⽚上的 TLB 缓存地址转换。主存(DRAM)⾥的转换表,⼜叫⻚表,处理缓存未命中情况。⻚表由 MMU(硬 件)直接读取。

互联

对于多处理器架构,处理器通过共享系统总线或者专⽤互联连接起来。共享的系统总线,称为前端总线,由早期的 Intel 处理器使⽤。现代服务器通常都是多处理器,NUMA,并使⽤ CPU 互联技术。⼀个四处理器系统的 Intel QPI 架构示例如图

CPU 性能计数器

CPU 性能计数器(CPU Performance co unter,CPC)有许多别名,包括性能测量点计数器(PIC)、性能监控单 元(PMU)、硬件事件和性能监控事件。它们是可以计数低级 CPU 活动的处理器寄存器。

评论

  1. 博主
    6 月前
    2025-7-30 0:09:21

    mark

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇