Skip to content

Lec 7 性能

前面讲在单一机器上通过虚拟化内存、有界缓存、线程来增强模块化;了解了微内核和宏内核的区别;探讨了如何在单一物理机器上通过虚拟机加强模块化,使其能够运行多个OS实例(并且其中一个OS发生了BUG不会影响到其他OS崩溃),其中如何实现VMM是一个关键问题,解决方法是通过“trap-and-emulate",陷入并模拟。一个关键问题是,如何陷入那些不会引发中断的指令。

现在还有什么没有讨论呢? 性能。这节将会介绍提供性能的通用方法。

本节没有参考书资料

思考题

  • 性能瓶颈是指什么?
  • 在思考系统性能时,拥有系统模型有何帮助?
  • 常见的性能指标有哪些?它们各自的含义是什么?它们之间的关系如何?
  • 哪些系统级技术通常能提升性能?
  • 硬盘读写操作是如何工作的:
    • 对于机械硬盘(HDDs)?
    • 对于固态硬盘(SSDs)?
  • 为什么在机械硬盘上批量读取能提升性能?
  • 假设我们要将一个大型数据库实现为一系列文件(即在文件系统之上实现数据库),我们需要考虑哪些事项?
  • 数据库管理系统(DBMS)擅长做什么?

lec7.md

性能指标

性能的依据是测量,测量需要指标:

  • 时延(Latency): 完成一个请求需要花费多长时间
  • 吞吐量(Throughput):单位时间内能处理多少请求
  • 利用率(Utilization): 资源被利用所占比重

他们的关系取决于上下文,一般来说,随着系统负载增加:

  • 一开始时延和吞吐量都很低,随着负载增加,吞吐量上升,而时延保持扁平
  • 直到系统到达最大吞吐量后,吞吐量停滞,时延增加。

对于需要高负载的系统,我们会更加关注吞吐量。

截屏2024-07-13 21.29.46截屏2024-07-13 21.30.39

以下几种技术是常用的瓶颈缓解手段:

  • 批处理
  • 缓存
  • 并发
  • 调度

硬盘工作原理

下面是HDD和SDD的组成图。我们先学习磁盘的工作原理:

  1. 磁盘由多个磁盘盘片(platters)组成,这些盘片安装在一个旋转轴(axle)上并高速旋转
  2. 每个盘片的两面都包含同心圆磁道(tracks),每个磁道有被划分为多个扇区(sectors)
    • 柱面(cylinder):位于不同的盘片上,垂直对齐的一组磁道
  3. 磁盘臂(disk arm)上有多个磁头(head),每个盘面对应一个磁头,所有磁头会同步移动
  4. 每个磁头在盘片旋转经过时,对扇区进行R/W操作
  5. 扇区大小是磁盘的基本读写单位,通常为512B
  6. 为了实现读写,该过程如下
    1. 磁头寻道(seek):移动磁盘臂,将磁头定位到目标磁道
    2. 等待旋转(rotational latency):等待盘片旋转到目标扇区进入磁头下方
    3. 数据传输:执行读写操作

截屏2024-07-13 21.38.16

一个磁盘的规格信息:

截屏2024-07-13 21.40.01

磁盘R/W需要花多长时间?

Solution:寻道时间: 读8.2ms, 写9.2ms;旋转时间: 0-8.3ms; 数据传输速率为 35~62MB/秒,因此随机读写一个4KB块所需时间:8.2ms + 4.1ms + ~.1ms = 12.4ms 吞吐量为 4KB/12.4ms = 322KB/s;99%的时间花在磁盘的机械移动上,而不是实际的数据传输。

性能优化方法

下面介绍几个磁盘性能优化方法。

批处理

  1. 使用Flash
  2. 批处理多个传输。
    1. 如果是顺序访问数据,效率会更高:
      • 寻道到相邻磁道: 0.8ms
      • 读取整个磁道: 8.3ms
      • 总时间: .8 + 8.3 = 9.1ms
    2. 单磁道数据量:
      • 1000 sector * 512 B/sector = 512KB
    3. 吞吐量计算: 512KB/9.1ms = 55MB/s

结论:尽可能避免随机访问,尽可能进行长时间的顺序读取。如果读写大文件,尽量将文件存储在磁盘上的连续区域;如果经常读取小数据块,应尽可能对数据进行分组,减少磁头移动次数,提高访问效率。

缓存

我们在 DNS 中已经见过缓存的应用。缓存是提升系统性能的常见方法。

如何衡量缓存的效果?

  • 平均访问时间 = 命中时间 * 命中率 + 未命中时间 * 未命中率

  • 目标:提高命中率(hit rate)

关键问题:如何选择要从缓存中淘汰(evict)哪些数据?

  • 最近最少使用(LRU, Least Recently Used)

缓存的适用场景:

  • 所有数据都能放进缓存(缓存足够大)
  • 数据访问具有局部性(Locality)

核心经验:构建高效缓存需要理解数据访问模式;就像优化磁盘性能一样,减少缓存成为瓶颈的关键在于理解底层工作原理

并发/调度

示例:

  • 5 个并发线程发出读请求,目标磁盘扇区分别是 71、10、92、45、29
  • 朴素算法:按请求顺序读取,每次都重新寻道,效率低。
  • 优化算法:按照磁道号排序,按顺序处理请求,提高吞吐量。
    • 问题:这种调度可能不公平(例如,总是优先处理某些请求)。

结论:调度没有唯一正确答案,需要在性能与公平性之间权衡。

并行化

目标:有多个磁盘,希望并行访问它们,提高性能。

关键问题:如何在多个磁盘之间分布数据?

情况 1:大量小文件请求,受磁盘寻道时间限制

✅ 解决方案:把每个文件放到单独的磁盘上,让多个磁盘可以并行寻道和访问不同文件。

情况 2:少量大文件请求,受顺序吞吐量限制

✅ 解决方案:使用条带化(Striping),把一个大文件拆分成多个部分,分布在多个磁盘上,提高顺序读写速度。

情况 3:跨多台计算机的并行存储

  • 问题:如何处理机器故障?

替代技术

SSD,通常是基于闪存(Flash Memory),但仍然提供了传统的磁盘接口。闪存的特点是:无机械部件。性能对比:

示例规格:

  • 顺序读取:400 MB/s
  • 顺序写入:200-300 MB/s
  • 随机 4K 读取:5700 次/秒(23 MB/s)
  • 随机 4K 写入:2200 次/秒(9 MB/s)

结论:

  • 顺序访问仍然远比随机访问块,
  • 写入性能明显更差(尤其是小块写入)
    • 闪存只能按大块擦除,小块写入时需要读取整个块-> 修改 -> 写回
    • 现代SSD通过复杂的控制器优化这个问题
  • 仍然可以通过批量操作(batching)减少小块写入

总结

  1. 不能盲目地应用优化技术,必须理解底层系统的工作原理。
  2. 批处理: 需要理解磁盘访问的工作方式。
  3. 缓存: 需要理解数据访问模式
  4. 调度、并发: 需要理解磁盘如何访问,系统的实际工作负载
  5. 并行: 要理解具体的工作负载类型

核心要点:优化系统性能的关键,在于深入理解系统的底层机制!

论文: Unix 分时系统(II)

The UNIX TimeSharing System

从第5章开始,阅读完后你应该知道在UNIX的进程的基本情况(比如,fork()如何工作,内存如何共享,进程如何通信);读完第6章,你应该知道shell的基本你请客,当你输入UNIXshell命令时,发生了什么?有多少进程参与了

文件系统

实现

一个目录项只包含一个关联文件的名称和一个指向文件本身的指针。这个指针是一个整数,称为文件的 i-number(index number)。当访问某个文件时,它的 i-number 将被用作索引,对一个由该目录所在设备的已知区域内所保存的系统表(即 i-list)进行查找。由此找到的条目(文件的 i-node)包含的文件描述如下:

  1. 所有者
  2. 保护位
  3. 物理磁盘或磁带存放文件内容的地址
  4. 文件大小
  5. 最后修改时间
  6. 文件的链接数,即该文件在目录中出现的次数
  7. 一个二进制位,表明该文件是否为目录
  8. 一个二进制位,表明该文件是否为特殊文件
  9. 一个二进制位,表明该文件是 “大文件” 还是 “小文件”

opencreate 系统调用的目的是通过搜索显式或隐式命名的目录,将用户给出的路径名转换成 i-number。一旦文件被打开,它的设备、i-number 和读写指针就会被存储在一张系统表中,该表由 opencreate 返回的文件描述符进行索引。因此,后续对文件调用读或写时提供的文件描述符,可以很容易地关联到访问文件所需的信息。

创建一个新的文件时,会给它分配一个 i-node,并建立一个包含文件名和 i-node编号(译注:即 i-number)的目录项。要链接到一个现有的文件,需要创建一个带有新名称的目录项,从原来的文件条目中复制 i-number,并递增 i-node 的链接数(link-count)字段。移除(删除)一个文件,则是通过递减其目录项所指向的 i-node 的链接数,并删除该目录项。如果链接数减到 0,文件占用的任何磁盘块都会被释放,i-node 也会被解除分配。

文件系统被划分为若干512字节的块,每个i-node上有一块区域可存放8个"设备地址",一个(非特殊)小文装进不大于8个块的大小,此时i-node上存的是块本身的地址。对于大文件,需要间接寻址,一个间接块可以存放256个块地址,所以最大能存8 * 256 * 512 = 2^20 个字节。

对于特殊文件,后7个"设备地址"没啥用,该列表被解释为一对构成内部设备(device)名称的字节。这些字节分别指定了设备类型和子设备号。设备类型表示该设备上的 I/O 将由哪种系统程序处理;子设备号则用于选择,例如,选择连接到某一控制器上的磁盘驱动器、选择数个打字机接口中的一个。

在这种环境下,mount 系统调用 (§3.4) 的实现非常简单。mount 维护了一个系统表(译注:一个映射关系),它的自变量是 mount 过程中指定的普通文件的 i-number 和设备名,对应的值是指定的特殊文件的设备名。在 opencreate 过程中扫描路径名时,会对每一组 (i-number,device) 进行搜索,如果发现匹配,i-number 就会被替换成 1(也就是所有文件系统中根目录的 i-number),设备名则替换成表中对应的值。

在用户看来,文件的读和写都是同步的,没有缓冲。也就是说,在 read 调用返回后,数据立即可以使用,反之,在 write 之后,用户的工作空间可以重新使用。实际上系统维护了一个相当复杂的缓冲机制,大大减少了访问文件所需的 I/O 操作次数。下面假设进行了一次 write 调用,指定传输一个字节

进程

shell

总结