固态硬盘(SSD)基于闪存技术已经在很大程度上取代了磁盘作为标准存储介质。从程序员的角度来看,SSD和磁盘在很多方面看起来很相似:它们都是持久性存储介质,通过文件系统和系统调用进行基于页面(例如4KB)的访问,并且具有较大的容量。
然而,它们也有重要的区别,如果想要实现最佳的SSD性能,则这些区别就变得重要起来。正如我们将看到的,SSD更加复杂,如果简单地将其视为快速磁盘,其性能行为可能会显得相当神秘。本文的目标是让人们了解SSD的行为原理,从而帮助开发能够充分利用SSD的软件。(请注意,我讨论的是NAND闪存,而不是具有不同特性的英特尔Optane内存。)
SSD通常被称为磁盘,但这是误导性的,因为它们将数据存储在半导体中,而不是机械磁盘上。要从随机块读取或写入数据,磁盘需要机械地将其磁头移动到正确的位置,这需要大约10毫秒的时间。相比之下,从SSD进行随机读取只需要约100微秒,快了100倍。正是由于低读取延迟,从SSD启动比从磁盘启动要快得多。磁盘和SSD之间的另一个重要区别是,磁盘只有一个磁头,在顺序访问时表现良好。相比之下,SSD由几十甚至数百个闪存芯片("并行单元")组成,可以同时访问。SSD会在页面粒度上透明地将较大的文件跨闪存芯片进行分布存储,并通过硬件预取器确保顺序扫描充分利用所有可用的闪存芯片。然而,在闪存层面上,顺序读取和随机读取之间的区别并不大。实际上,对于大多数SSD来说,通过随机页面读取也几乎可以达到完整的带宽。要做到这一点,需要同时调度数百个随机IO请求,以保持所有闪存芯片的繁忙状态。可以通过启动大量线程或使用异步IO接口(如libaio或io_uring)来实现这一点。写入操作变得更加有趣。例如,如果我们看写入延迟,可以测量到低至10微秒的结果,比读取快10倍。然而,延迟看起来如此低只是因为SSD在易失性RAM上缓存写入操作。实际的NAND闪存写入延迟约为1毫秒,比读取慢10倍。在消费级SSD上,可以通过在写入后发出同步/刷新命令来测量这一点,以确保数据在闪存上持久保存。在大多数数据中心/服务器SSD上,无法直接测量写入延迟:同步/刷新操作将立即完成,因为电池可以保证在断电情况下写入缓存的持久性。为了在相对较高的写入延迟下实现高写入带宽,写入操作使用与读取操作相同的技巧:同时访问多个闪存芯片。由于写入缓存可以异步写入页面,因此甚至不需要同时调度太多的写入操作即可获得良好的写入性能。然而,写入延迟无法完全隐藏:例如,由于写入操作占用一个闪存芯片的时间比读取操作长10倍,因此对同一个闪存芯片的读取操作会导致显着的尾延迟。我们的理解缺少一个重要事实:NAND闪存页面无法直接进行覆盖写入。页面写入只能在事先擦除的块内按顺序执行。这些擦除块的大小为多个MB,因此由数百个页面组成。在新的SSD上,所有块都被擦除,可以直接开始追加新数据。然而,更新页面并不那么简单。为了就地覆盖写入一个单独的页面,整个块进行擦除将会非常昂贵。因此,SSD通过将页面的新版本写入新位置来执行页面更新。这意味着逻辑页面地址和物理页面地址是分离的。存储在SSD上的映射表将逻辑(软件)地址转换为物理(闪存)位置。这个组件也称为闪存转换层(FTL)。例如,假设我们有一个(玩具)SSD,它有3个擦除块,每个块有4个页面。对页面P1、P2、P0、P3、P5、P1的一系列写入操作可能会导致以下物理SSD状态:| Block 0 | P1 (old) | P2 | P0 | P3 |
| Block 1 | P5 | P1 | → |
|
| Block 2 |
|
|
|
|
使用映射表和非就地写入,一切都很好,直到SSD用尽空闲块。最终,被覆盖的旧版本页面必须被回收。如果我们继续上面的例子,写入页面P3、P4、P7、P1、P6、P2,我们得到以下情况:| Block 0 | P1 (old) | P2 (old) | P0 | P3 (old) |
| Block 1 | P5 | P1 (old) | P3 | P4 |
| Block 2 | P7 | P1 | P6 | P2
|
此时,我们没有更多的空闲擦除块(尽管从逻辑上来说应该还有空间)。在可以写入另一个页面之前,SSD首先必须擦除一个块。在这个例子中,对于垃圾回收器来说,擦除块0可能是最好的选择,因为只有一个页面仍在使用中。在擦除块0之后,我们为3个写操作腾出了空间,我们的SSD看起来是这样的:
| Block 0 | P0 | →
|
|
|
| Block 1 | P5 | P1 (old) | P3 | P4 |
| Block 2 | P7 | P1 | P6 | P2
|
为了回收块0,我们不得不物理移动页面P0,尽管从逻辑上来说该页面没有发生任何变化。换句话说,对于闪存SSD,物理(闪存)写入的数量通常比逻辑(软件)写入的数量更多。两者之间的比率被称为写放大。在我们的例子中,为了在块0中为3个新页面腾出空间,我们不得不移动1个页面。因此,我们有了3个逻辑写入对应的4个物理写入,即写放大比为1.33。高写放大会降低性能并缩短闪存的寿命。写放大的大小取决于访问模式和SSD的填充程度。大型顺序写入的写放大较低,而随机写入是最糟糕的情况。假设我们的SSD已经填充到50%,并进行随机写入。在稳定状态下,无论我们擦除哪个块,大约一半的页面仍在使用中,平均而言必须进行复制。因此,50%填充因子的写放大为2。一般而言,填充因子f的最坏情况写放大为1 (1-f):| f | 0.1 | 0.2 | 0.3 | 0.4 | 0.5 | 0.6 | 0.7 | 0.8 | 0.9 | 0.95 | 0.99 |
| WA | 1.11 | 1.25 | 1.43 | 1.67 | 2.00 | 2.50 | 3.33 | 5 | 10 | 20 | 100
|
由于填充因子接近1时写放大变得不合理高,大多数SSD都有隐藏的备用容量。这种过度配置通常是总容量的10-20%。当然,也可以通过创建一个空分区并永远不对其进行写入来增加更多的过度配置。
SSD价格相对较低,性能非常高。要实现这样高的性能,需要了解SSD的工作原理,而本文描述了闪存SSD的最重要的幕后机制。由于SSD变得如此快速,操作系统I/O栈通常是性能瓶颈。