EROFS-华为超级文件系统浅析——(一)论文阅读

EROFS文件系统浅析——(一)论文阅读

注:本文参考《EROFS: A Compression-friendly Readonly File System for Resource-scarce Devices》


摘要

智能手机通常存储和运行时内存有限。压缩只读文件系统可以显著减少只读系统资源使用的存储空间。然而,现有的压缩只读文件系统使用固定大小的输入压缩,这会导致显著的I/O放大和不必要的计算。在解压缩过程中,它们还会消耗过多的运行时内存,并在运行时内存不足时降低性能。在本文中,我们描述了EROFS,这是一种新的压缩友好的只读文件系统,它利用固定大小的输出压缩和内存有效的解压缩来实现高性能,而无需额外的内存开销。我们还报告了在数千万智能手机上部署EROFS的经验。评估结果表明,EROFS在各种微基准测试中优于现有的压缩只读文件系统,并将实际应用程序的启动时间减少了22.9%,同时将存储使用率减少了近一半。


介绍

价格相对较低的低端智能手机仍然在市场上流行,特别是在发展中国家。这样的设备通常在容量和性能方面都配备有有限的资源。例如,低端Android智能手机可能有1-2GB运行时内存和8-16GB慢速eMMC存储。更糟糕的是,Android操作系统本身可能会消耗超过3GB的存储空间,从而为用户留下稀缺的存储空间。即使对于高端智能手机,流行或常驻应用程序不断增加的存储和运行时内存消耗通常会导致用户启动和系统启动操作的设备资源稀缺。

具有压缩支持的文件系统或压缩文件系统可用于通过在访问时透明地压缩/解压缩文件数据来向用户释放更多空间。然而,这样的文件系统通常消耗更多的资源,并且在压缩/解压缩期间产生明显较差的性能。因此,它们不适用于资源有限的设备,尤其是用户体验最为重要的智能手机。幸运的是,对于具有只读数据的分区,例如Android的/system、/vendor和/odm分区,可以通过简化文件更改的结构和设计,将文件系统设置为只读,以提高性能。然而,现有的压缩只读文件系统(如Squashfs)通常会导致访问性能显著下降,并在解压缩期间产生额外的内存使用。一个关键问题是这样的文件系统使用固定大小的输入压缩,其中文件数据被分成固定大小的块(例如128KB),并且每个块被单独压缩。固定大小的输入压缩导致显著的读取放大和过度不必要的计算。更糟糕的是,它们通常需要大量的运行时内存,这在低端智能手机或大量使用的高端智能手机上是稀缺的。

为了节省存储空间并以低内存开销保持高性能,我们设计并实现了EROFS,这是一种支持压缩的增强型只读文件系统。EROFS引入了固定大小的输出压缩,将文件数据压缩为多个固定大小的块,以显著缓解读取放大问题并尽可能减少不必要的计算。通过利用压缩算法(如LZ4)的特性,EROFS设计了不同的内存效率解压缩方案,以减少解压缩过程中的额外内存使用。EROFS还采用了一系列优化措施,以确保用户体验得到保证。

本文的主要贡献包括:
•对现有压缩文件系统的研究,揭示了资源匮乏设备的性能问题。
•一个固定大小的输出压缩方案,显著缓解了读取放大问题。
•一套新颖的解压方案,既提高了存储效率,又提高了性能。
•针对其他文件系统对EROFS进行评估,以验证EROFS的有效性,并对数千万智能手机上的EROFS部署经验进行研究。


背景和动机

低用户感知存储空间

由于成本限制,智能手机通常资源稀缺。与此同时,Android操作系统占用的空间不断增加。图1显示了不同Android版本的库存Android工厂镜像中的/system分区大小。稀疏镜像去除所有零块,因此仅包含所有有效数据;而裸镜像是存储在设备中消耗的实际空间。从图中,我们可以看到/system分区的数据大小从Android 2.3.6中的184MB增加到Android 9.0.0中的1.9GB。除了增加有效数据大小的趋势之外,我们还可以看到Android 7和8中的大量零块,这也消耗了大量空间。对于Android 9,零块明显更少,这是由于ext4文件系统中支持数据块重复数据消除。除了图1所示的/system分区之外,Android还有其他占用空间的分区,例如/vendor、/oem和/odm。正如之前的工作所报告的,整个Android系统本身使用的空间正在增加,远远大于我们在这里展示的空间。例如,Android 6.0.0在出厂重置后消耗3.17GB存储空间。

与此同时,Android应用程序的存储消耗也在不断增长。据Google Play报道,到2017年初,平均应用程序规模已比谷歌启动Android应用程序市场时的规模翻了五倍。因此,可供用户使用的低端智能手机的存储容量相当小。此外,智能手机的许多顶级应用程序往往会消耗大量内存,即使在高端智能手机上,系统启动操作也只剩下少量内存。
在这里插入图片描述
压缩文件系统 为用户释放更多空间的一种直观方法是采用压缩文件系统,它向应用程序公开标准文件接口,但在文件写入和读取期间透明地压缩和解压缩文件数据。

Btrfs是一种支持压缩的现代B树文件系统。启用压缩后,文件数据将被分成多个128KB的块并分别压缩。每个压缩块将存储在一个区段中,该区段是顺序存储数据的连续块的运行。这些区段的位置记录为B树结构中的索引。为了读取文件数据,从存储器中读取相应的数据块,并解压缩整个块。要更新文件,将新数据压缩并写入新的区段,然后更新索引。为了读取文件数据,Btrfs从存储中读取相应的数据块并解压缩整个数据块。要更新文件,Btrfs将压缩新数据,将其写入新数据块,并更新索引。

Btrfs是一个通用文件系统,因此其内部结构必须考虑有效的数据修改,并且不能针对压缩进行积极优化。此外,压缩不是唯一的度量。解压缩期间的内存消耗也应受到限制。

对于智能手机等设备,性能和响应能力是重要的关键指标,不容妥协。因此,在高效数据修改的负担下,Btrfs很难满足性能和压缩效率的要求,我们将在后面的评估中说明这一点。

压缩只读文件系统 考虑到Android中分区的访问模式,我们发现一旦安装了Android操作系统,系统资源很少被修改。因此,我们可以在只读分区上使用压缩只读文件系统,以减少系统资源的空间消耗,同时保持性能。与因数据修改而变得复杂的压缩读写文件系统不同,压缩只读文件系统在设计上排除了数据更新,这为更高的压缩比和更快的数据读取提供了更多机会。

Squashfs是Linux中广泛使用的压缩只读文件系统,具有许多功能和中等性能。它支持多种压缩算法,块(即压缩输入)大小可以从4KB到1MB之间选择。在Squashfs中,元数据可以被压缩,索引节点和目录存储得更紧凑。文件数据被逐块压缩,并且压缩的数据块被顺序存储。每个原始数据块的压缩大小存储在索引节点内的列表中。这些大小用于在解压缩期间定位压缩块的位置。

现有只读文件系统的缺陷

压缩只读文件系统旨在最大限度地减少存储使用。然而,在资源稀缺的智能手机上应用现有的压缩只读文件系统可能会导致性能和内存消耗方面的显著开销。例如,我们首先尝试将Squashfs用于Android上的只读分区。虽然系统使用Squashfs成功启动,但即使在背景工作负载较轻的情况下,启动相机应用程序也需要数十秒。

为什么会出现如此巨大的性能下降?我们使用微基准对默认配置的Squashfs进行了详细研究,发现性能下降主要来自两个部分。第一个是I/O放大。我们使用FIO来评估Squashfs的基本性能。当Android从存储在Squashfs中的1GB enwik9文件中顺序读取16MB时,实际发出的I/O为7.25MB。虽然这个数字在压缩方面看起来不错,但当Android随机读取16MB时,Squashfs会发出165.27MB的I/O读取。此外,当Android读取每128KB中的第一个4KB时,读取16M文件数据会发出多达203.91MB的I/O读取。这种差异表明,当Squashfs读取一些以前未解压缩和缓存的数据时,请求的数据大小会显著放大。

第二个原因是额外的内存消耗。在Squashfs上顺序读取1GB enwik9文件后的总内存消耗约为1.35GB,这表明与所需原始数据的大小相比,Squashfs中的解压缩需要大量的临时内存。这给Android带来了巨大压力,因为内存是用户体验的关键因素,因为Android及其应用程序已经消耗了大量内存。一方面,在解压缩期间分配内存可能会触发内存交换,这涉及受害者选择和I/O,成本很高。另一方面,在解压缩过程中消耗大量额外内存会影响其他组件或应用程序,因为它们会丢弃缓存的数据或交换有用的内存页。
在这里插入图片描述
我们进一步分析了Squashfs的设计和实现,发现了以下两个缺陷。

固定大小的输入压缩 现有文件系统将原始数据压缩到固定大小的块中,生成可变大小的压缩数据。如图2(a)所示,Squashfs将固定大小的数据(例如,128KB块)作为压缩算法的单个调用的输入。然后,压缩算法生成压缩数据,其大小取决于输入数据的内容。一个文件的压缩数据通常按原始数据顺序压缩,以减少每个压缩块的第一个和最后一个块中的浪费空间。

这种压缩方法看起来不错,但由于放大的I/O和浪费的计算,存在明显的缺陷。例如,在图2(b)中,应用程序希望获得128KB块的第一个字节。为了满足应用程序的要求,Squashfs必须读取从块1到块7的所有压缩数据。考虑到底层存储设备的最小请求块大小为4KB,I/O放大了7倍!这是因为文件系统必须读取所有相关的压缩块,即使压缩块的数量非常大。更糟糕的是,即使存储在第一个块和最后一个块中的所有数据对解压缩都有用,也必须从存储中完全读取它们。在该示例中,图2(b)中块1和块7的阴影部分对解压缩没有贡献,但必须从存储器中读取。此外,对无用数据的解压缩过程也会造成巨大的CPU浪费,导致对其他正在运行的应用程序(如前面提到的摄像头)的高性能干扰。

一种可能的缓解措施是在Squashfs中将输入块大小减小到4KB。虽然这可能会减轻I/O放大,但这会大大降低压缩比并导致更高的CPU利用率,如我们将在第5节中所示。

大量内存消耗和数据移动。我们发现的另一个缺陷是,Squashfs在解压缩期间需要大量临时内存。在文件读取请求时,Squashfs将首先查找元数据以获得相关压缩块的数量。然后,它为每个压缩块分配内存(例如,buffer_head结构),并发出I/O读取以将压缩块从存储器提取到分配的buffer-heads。由于相邻压缩块的buffer_heads中的缓冲区可能没有连续的虚拟地址,因此Squashfs必须将所有压缩块的buffer_ heads中数据复制到单个连续缓冲区。然后,压缩算法解压缩所有原始数据,并将它们放入临时输出缓冲区。最后,Squashfs将原始数据从临时输出缓冲区复制到相应的页面缓存页面。

根据上述例程,使用两个预先分配的临时缓冲区,并动态分配一组buffer_heads用于解压缩。buffer_ head的数量需要足够大以存储所有压缩块。然而,在内存不足的情况下,分配如此大量的内存可能会导致严重的性能下降。

除了额外的内存分配,解压过程中还有两个数据移动:从buffer_heads到临时输入缓冲区,以及从临时输出缓冲区到页面缓存。这两种数据移动也会导致性能开销,因为在大多数情况下,压缩/解压缩算法会受到内存访问的限制。

在为资源稀缺的智能手机设计压缩只读文件系统时,Squashfs中的上述两个缺陷揭示了两个挑战。
•如何在不牺牲压缩比的情况下减少解压缩过程中的I/O放大?
•如何在解压缩过程中减少内存消耗以防止性能下降?


EROFS:增强的压缩文件系统

本节介绍了EROFS的设计,这是一种压缩友好的只读文件系统,它克服了现有系统的不足。EROFS的关键设计包括固定大小的输出压缩、缓存I/O和就地I/O以及内存有效的解压缩。


固定大小输出压缩

为了克服固定大小输入压缩引起的读取放大,EROFS采用了不同的压缩方法:固定大小输出压缩。

为了生成固定大小的输出,EROFS使用滑动窗口压缩文件数据,滑动窗口的大小是固定值,可以在镜像生成期间配置。压缩算法被多次调用,直到所有文件数据被压缩。例如,对于1MB滑动窗口,EROFS一次向压缩算法提供1MB原始数据。然后,该算法尽可能压缩原始数据,直到消耗掉所有1MB数据,或者消耗的数据可以生成精确的4KB压缩数据。剩余的原始数据与更多数据组合,形成另一个1MB原始数据,用于下一次调用压缩。图2(c)描述了固定大小的输出压缩,其中可变大小的原始数据被压缩为4KB块。

与固定大小的输入压缩相比,使用固定大小的输出压缩有几个好处。首先,正如我们在评估中所展示的,在相同的压缩单元大小下,它具有更好的压缩比。这是合理的,因为固定大小的输出压缩可以尽可能多地压缩数据,直到达到输出限制,而固定大小的输入压缩一次只能压缩固定大小的数据。第二,在解压缩期间,仅读取和处理包含所请求数据的压缩块。在请求单个原始块的前一示例中,最多将读取和解压缩两个压缩块。第三,固定大小的输出压缩使就地解压缩成为可能,这进一步减少了EROFS中的内存消耗。


Cached I/O and in-place I/O

在实际解压缩之前,EROFS需要空间来存储从存储中检索的压缩数据。虽然由于内存分配过多甚至页面交换,固定大小的输入压缩成本很高,但固定大小的输出压缩成本要低得多,因为EROFS清楚地知道,每次压缩最多只能检索两个压缩块。EROFS有两种策略:缓存I/O和就地I/O。EROFS对将被部分解压缩的压缩块使用缓存I/O。EROFS管理一个特殊的索引节点,其页面缓存存储按物理块编号索引的压缩块。因此,对于缓存的I/O,EROFS将在特殊inode的页面缓存中分配一个页面,以启动I/O请求,从而存储驱动程序将压缩数据直接提取到分配的页面。对于将完全解压缩的压缩块,EROFS使用就地I/O。每次读取文件时,VFS将在页面缓存中为文件系统分配页面,以放置文件数据。对于任何一个在解压之前不包含有意义数据的页面,我们称之为可重用页面。对于就地I/O,EROFS使用最后一个可重用页面来初始化I/O请求。

两种I/O策略都是必要的。对于缓存的I/O,部分解压缩的块缓存在特殊页面缓存中,因此对未压缩部分的后续读取可以使用这些块,而无需调用其他I/O请求。对于完全解压缩的块,它们不太可能在以后使用,因为所有解压缩的数据都存储在页面缓存中,页面缓存可以在不解压缩的情况下为后续读取提供服务。因此,缓存的I/O徒劳地增加了内存峰值,这是由于对完全压缩的块进行了页面分配,而不会导致后续的文件读取。在这种情况下,就地I/O避免了不必要的内存分配,这减轻了内存压力,特别是当在不同压缩块上有许多正在进行的文件读取请求时。注意,虽然可以将压缩块放在堆栈上,但不建议这样做,因为堆栈大小限制为16KB,并且不容易知道堆栈中还有多少字节可用。


解压

在这里插入图片描述

在将压缩数据加载到内存后,我们将演示EROFS如何快速、高效地解压缩数据。本节中的示例基于图3(a),其中前五个块(D0至D4)和块D5的一部分被压缩为块C0,其余块被压缩至块C1。在本小节中,我们仅介绍如何对单个压缩块进行解压缩,因为对于包含多个压缩块中的数据的读取请求,类似地,一个接一个地解压缩压缩块。例如,为了读取图3(a)中的块D4至D6,首先对C0进行解压缩以获得D4和D5的第一部分;然后C1被解压以得到D5和D6的其余部分。

Vmap解压缩
为了获得块D3和D4中的数据,EROFS首先从存储器中读取压缩块C0并将其存储在存储器中。然后EROFS将在以下步骤中对其进行解压缩:
1.找到存储在压缩块(C0)中的最大所需块号,该压缩块是示例中的第五块(D4)。作为一个优势,EROFS只需要解压缩前五个块(D0到D4),而不是解压缩所有原始数据块。
2.对于需要解压缩的每个数据块,找到存储它们的内存空间。在图3(b)所示的示例中,EROFS分配三个临时物理页以存储D0、D1和D2。对于请求的两个块D3和D4,EROF重用页面缓存中由VFS分配的两个物理页。
3.由于解压缩算法需要连续内存作为解压缩的目的地,EROFS通过vmap接口将在前一步骤中准备的物理页面映射到连续虚拟内存区域。
4.如果是就地I/O,在这种情况下,压缩块(C0)存储在页面缓存页面中,EROFS还需要将压缩数据(C0)复制到临时perCPU页面,以便在解压缩期间解压缩数据不会覆盖压缩数据。
5.最后,调用解压缩算法,将压缩块中的数据提取到连续存储区域。在压缩之后,可以回收三个临时物理页和虚拟内存区域,并且请求的数据已经被写入相应的页缓存页。

每CPU缓冲区解压缩
上述解压缩方法导致两个问题。第一个问题是,仍然需要动态分配物理内存页,这增加了内存受限设备的内存压力。第二个问题是在每次解压缩时使用vmap和vunmap效率低下。
当解压缩数据少于四页时,EROFS利用每CPU缓冲区来缓解问题。如图3(c)所示,为每个CPU预先分配四页存储器缓冲器作为每个CPU缓冲器。对于提取不超过四个数据块的解压缩,EROFS将数据解压缩到每CPU缓冲区,然后将请求的数据复制到页面缓存页面。在图3(c)所示的示例中,请求块D8中的数据。C1中的压缩数据直接解压缩到每CPU缓冲区,D8的内容通过memcpy复制到页面缓存页面。

每个CPU缓冲区的长度是根据经验确定的,但它可以有效地消除内存分配,因为每个CPU的缓冲区可以在不同的解压缩中重用。每CPU缓冲区解压缩是一种经济高效的折衷方案,它在引入额外内存副本的同时缓解了vmap解压缩中的问题。

滚动解压缩
为了避免vmap和vunmap的开销并消除其他动态页面分配,EROFS为每个CPU分配了一个大的虚拟内存区域^2和16个物理页面。

在每次压缩之前,EROFS使用16个物理页面以及页面缓存的物理页面来填充VM区域,因此可以跳过vmap解压缩的步骤2和步骤3。

EROFS使用LZ4作为压缩算法,它需要向后查看不超过64KB的解压缩数据。因此,对于提取超过16个页面的压缩,EROFS可以重用之前映射到16个虚拟页面(即64KB)的物理页面。例如,在图4中,存储块D0至D15的虚拟地址由16个物理页支持。D16的虚拟页可以由与D0相同的物理页进行备份,因为D16中的每个虚拟地址与D0中的相应地址相差64KB。D17以与D1使用的物理页相同的方式进行备份。文件读取请求的D18使用页缓存的物理页。

结果,通过使用这种滚动解压缩,16个物理页对于任何解压缩情况都是足够的。
在这里插入图片描述
就地解压缩
在vmap解压缩方法的步骤4中,压缩数据被移动到临时的每CPU页面,以避免尚未压缩的数据被压缩数据覆盖(图5)。
在这里插入图片描述
但是,如果压缩块永远不会出现这种情况,我们可以就地解压缩它,以避免额外的内存分配和内存复制。EROFS模拟mkfs期间的解压缩,并标记压缩块可以在块索引中就地解压缩。在可就地解压缩的块的解压缩过程中,跳过步骤4。在§5中测试的工作负载enwik9中,99.6%的压缩块可以就地解压缩;因此,只要通过就地I/O检索,大多数块都可以受益于就地解压缩。


实施

我们将EROFS实现为一个Linux文件系统,并将EROF的公共部分开源到Linux内核。

在当前实现中,我们使用4KB作为固定输出大小,因为它是页面管理和存储数据传输的最小单位,因此可以最小化I/O放大。我们支持LZ4(v1.8.3)作为压缩算法,因为它在我们的情况下具有最快的解压缩速度和良好的压缩比。其他压缩算法,如LZO,一旦被修改以提供固定大小的输出压缩接口,就可以支持。在EROFS中仅压缩文件数据;存储索引节点和目录项等元数据时不进行压缩。

目前,EROFS仍在积极开发中,经过严格的商业测试过程后,新功能不断地进入智能手机。因此,我们介绍了EROFS的两个版本:1)最新版本,具有本文中介绍的所有功能和优化;2) 商业部署版本,除滚动解压缩和就地解压缩外,具有所有功能和优化。这两个版本的解压策略也不同,我们将在§4.2中说明。


EROFS镜像布局

在这里插入图片描述
图6示出了EROFS图像的布局。与其他文件系统一样,超级块位于镜像的开头。在超级块之后,元数据和数据可以以混合样式存储,而不受顺序限制。

在当前实现中,文件的元数据和数据存储在一起,以实现更好的局部性。对于每个文件,如图6所示,索引节点存储在开头,后面是包含扩展属性(即,Xattr)和块索引的块。压缩或未压缩文件数据的块(编码块)存储在每个文件的末尾。

由于索引节点可以放置在镜像中的任何位置,因此根据索引节点的位置计算索引节点编号,以便快速定位索引节点。如果文件不包含xattrs或未压缩,则会省略xattrs的块和块索引。此外,如果可能,xattrs、块索引和文件数据也可以内联在索引节点内,这减少了存储开销并减少了I/O请求的数量,因为内联的数据/元数据与索引节点一起读取。

块索引用于快速定位读请求的对应编码块。图6示出了在压缩之前包含十个块的常规文件的示例块索引。块索引是一个8B长度条目的数组,每个条目对应于压缩前的数据块。每个条目指示对应的数据块是否是开始新编码块的头块(图6中的布尔头字段)。如果是,还存储编码块地址(blkaddr)、新编码块中第一个字节的偏移量(偏移量)、编码块是否被压缩(cmpr)以及该块是否可以就地解压缩(dip)。如果不是,则在未压缩块之前必须有一个头块,并且与头块的块号差异记录在dist中。

对于对未压缩数据块的读取请求,EROFS根据请求的块编号获取块索引条目。对于头块,EROFS从blkaddr处的块中解压数据,如果偏移量为非零,则EROFS可能还需要从BLKADD之前存储的最近编码块中解。对于非头部块,EROFS根据存储的dist计算相应头部块的位置,并开始解压缩,直到请求的块数据被解压缩。

压缩后较大的一些数据块(例如,图6中的块5)未被压缩并直接存储为编码块。对于这些情况,相应的cmpr字段设置为假(即图中的“N”)。

目录的存储方式与常规文件类似,只是没有块索引,编码块用于存储未压缩的目录条目。为了更好地定位目录中的随机访问,EROFS将所有目录头(例如,索引节点编号、文件类型和名称长度)放在目录项部分的开头,并将文件名放在这些头之后。


解压策略

两个版本的EROFS具有不同的解压缩策略。如果要提取的原始数据块少于四个,则商业部署的EROFS使用每CPU缓冲区解压缩;否则,将使用vmap解压缩。

在最新的EROFS中,实现了所有四种解压缩方法。如果要提取的数据块不超过一个,则选择每CPU缓冲区解压缩。否则,如果使用就地I/O检索压缩块并可以就地解压缩,EROFS将采用就地解压缩方法,避免不必要的内存分配和内存复制。对于其他情况,在预分配的VM区域中可以容纳解压缩块,EROFS使用滚动解压缩,因为它比vmap解压缩具有更少的内存分配开销。对于任何其他情况,采用vmap解压缩方法。


优化

索引内存优化 EROFS有可能将数百页原始数据压缩成单个压缩块。在这种情况下,EROFS需要数百个指针来跟踪原始数据的每页应该存储在哪里。这些指针会消耗大量内存。为了解决这样的问题,EROFS尝试在可重用页面的帮助下存储信息。如果有多个VFS分配的页面是可重用的,EROFS将使用最后一个页面来存储压缩数据,而其他页面将在I/O期间存储其中一些指针。在实际解压缩之前,这些指针被移动到堆栈上,这样可重用页面就可以自由地存储解压缩的数据。

调度优化 解压缩需要相对较长的时间。因此,不适合在中断上下文中进行。在某些文件系统(如Btrfs)中,当压缩数据被提取到内存时,将唤醒一个专用线程来解压缩数据。解压完成后,发出I/O的读取器线程将被唤醒,以从页面缓存中获取解压数据。为了减少调度开销,EROFS在读取器线程中解压缩数据,而无需专用线程进行解压缩。因此,一旦压缩数据被提取到内存,阅读器线程将被直接唤醒并开始解压缩数据。

队列减压 多个请求可以同时进行。如果在线程A上请求原始数据块,并且对应的压缩块正由另一个名为线程B的线程解压,而不是单独解压数据,则线程A可以等待线程B完成解压,然后直接从页面缓存中读取解压数据。这种协作重用解压缩数据并防止单个数据被多次解压缩。

镜像修补 虽然EROFS是一个压缩只读文件系统,但在某些情况下,如系统升级或安全修补,需要更新存储在EROFS中的数据。EROFS提供了一种称为图像修补的功能,支持部分数据更新。通常,修改原始文件数据中的单个位可能会导致压缩数据中的大量零散修改。代替就地修改,图像修补将更新的数据放置在EROFS图像的末尾,当请求相应的文件数据块时,首先解压缩原始数据块,然后应用更新的数据覆盖存储器中的解压缩数据。通过这种方式,图像修补可以防止更改分散,并支持部分数据更新,而无需重新压缩当前文件系统。


评价

我们进行了一系列实验来回答以下问题:
•压缩如何影响文件系统读取访问的性能?
•在解压缩过程中,EROFS消耗了多少内存?
•EROFS如何影响真实应用程序的启动时间?


评估设置

默认情况下,我们在ARM开发板HiKey 960上进行实验,该板运行Linux内核4.14的Android 9 Pie。该板配备Kirin 960(四个Cortex-A73大核和四个Corex-A53小核)、3GB Hynix LPDDR4内存和32GB三星UFS存储。在一些实验中,我们还对两种智能手机进行了评估。低端智能手机配备了MT6765(八个Cortex-A53内核)、2GB内存和32GB eMMC存储。高端智能手机采用麒麟980(四个Cortex-A76核和四个Corex-A55核)、6GB内存和64GB UFS存储。

对于微基准测试,我们在各种文件系统上运行FIO,一种灵活的I/O测试仪,包括EROFS、Squashfs、Btrfs、Ext4和F2FS。我们使用最新版本的EROFS进行微基准评估。在这些文件系统中,EROFS和Squashfs被设计为压缩只读文件系统;Btrfs是一个支持压缩的文件系统,但它不是专为只读数据设计的文件系统;Ext4是Android使用的默认文件系统;F2FS是专为手机设计的文件系统,广泛应用于一些智能手机。

EROFS被配置为与LZ4一起使用4KB大小的输出压缩。Squashfs被配置为将LZ4与4KB、8KB、16KB和128KB压缩块大小一起使用,分别由Squashfs-4K、Squashf-8K、Shushfs-16K和ShushFS-128K表示。Btrfs配置为在只读模式下运行,无需进行数据完整性检查以进行公平比较。Btrfs使用的压缩算法是LZO,因为Btrfs不支持LZ4。在实验中,Ext4和F2FS都不支持压缩,因为它们不支持LZ4。

对于现实应用程序,我们将EROFS与Ext4进行比较,因为Ext4现在是Android使用的默认文件系统。我们使用商业版进行实际评估,因为将最新版本发布到智能手机需要时间。我们还尝试在Android上使用Squashfs。然而,它耗费了太多的CPU和内存资源,当试图运行一个摄像头应用程序时,手机冻结了数十秒,最终失败。


微观基准

在这里插入图片描述
我们使用FIO来显示不同文件系统的基本I/O效率。在本实验中,我们使用enwik9作为工作负载,这是英语维基百科转储的前109个字节。我们将文件存储在不同的文件系统中,并读取文件以测试文件系统读取吞吐量。每次读取都是4KB缓冲读取。我们在三种情况下测试吞吐量:顺序读取、随机读取和跨步读取。对于顺序读取,我们以4KB乘4KB顺序读取文件;因此,由于数据已通过先前的解压缩或预取(即,提前读取)加载到内存中,因此以下读取极有可能命中缓存。对于随机读取,我们随机读取整个文件;因此,如果数据已经被以前的读取解压缩,则读取可以命中缓存。最后一个场景是步幅读取,在该场景中,我们只读取每128KB数据中的前4KB。由于最大的压缩块是128KB,所以在缓存4中不会出现跨步读取。我们测试跨步读取以说明压缩文件系统的最坏情况性能。

在每次测试之前,会删除页面缓存以减少干扰。所有测试至少进行十次,并报告平均吞吐量。A53岩心上步幅读数的最大相对标准偏差为17.3%,其余结果为5.1%。图7显示了我们观察到的以下结果。与EROFS和Squashfs-128K相比,Btrfs在所有测试中表现最差,因为它既不用于压缩,也不用于只读数据。一方面,Btrfs不利用只读属性,必须考虑更新;因此,压缩只读文件系统EROFS和Squashfs-128K的性能优于它。另一方面,与Ext4和F2FS相比,Btrfs中的解压缩会带来显著的性能开销,因为它们在读取期间不需要解压缩数据。这是合理的,因为Btrfs并非设计为压缩只读文件系统。

对于顺序读取,Btrfs的性能优于其他Squashfs配置,这是由于其更大的压缩块(128KB)造成的。在预取不起作用的随机读取中,优势缩小;这种优势在跨步读取中消失了,在跨步阅读中,解压缩比请求的数据更多的数据成为负担。

总体而言,该结果显示了使用具有只读数据压缩支持的通用文件系统的效率低下,并强调了设计压缩只读文件系统的必要性。

随着压缩输入大小的增加,对于随机读取和顺序读取,Squashfs的性能增加,但对于跨距读取,其性能降低。这种现象的主要原因是局部性和缓存。由于文件系统有足够的内存来缓存本实验中的文件数据,所有解压缩的数据都将被缓存,将来可能会被读取。因此,对于随机读取和顺序读取,解压缩的数据越大,将来的读取将命中缓存。这就是为什么压缩块大小增加时,Squashfs吞吐量增加的原因。

由于顺序和随机读取都将读取整个文件,因此性能差异很小,这是由良好的局部性和预取造成的。

然而,对于跨步读取,FIO只读取每个128KB数据的前4KB数据,这消除了内存缓存的好处,因为所有解压缩但未请求的数据将来都不会使用。因此,读取和解压缩的无关数据越多,浪费的时间和资源就越多,性能也就越差。这解释了为什么吞吐量随着压缩块大小的增加而下降。

在支持压缩的文件系统中,EROFS在大多数测试中表现最好,有时甚至优于不压缩数据的文件系统。对于顺序读取,EROFS在压缩文件系统中表现出最佳性能。大多数胜利来自于固定大小的输出压缩设计,与Squashfs相比,消除了不必要的内存分配和数据移动。对于随机读取,EROFS的性能优于Squashfs-128K,因为后者可以在测试期间解压缩和缓存整个文件,而EROFS仅从缓存的I/O中受益。然而,EROFS仍然比其他压缩文件系统性能更好。对于跨步读取,由于预取几乎没有什么用处,EROFS仍然在压缩文件系统中产生最佳吞吐量,但胜算有限。

与不支持压缩的Ext4和F2FS相比,EROFS的性能始终与它们相当,甚至优于它们(例如,A73核上的顺序读取)。原因是,即使EROFS需要解压缩数据,由于压缩,它从存储中读取的数据也要少得多。
在这里插入图片描述
我们还使用工作负载silesia。以进行相同的实验。西里西亚。tar是西里西亚压缩语料库的tarball,它涵盖了当前使用的典型数据类型。结果显示了与enwik9相同的趋势,因此我们仅在图8中展示了2362MHz下A73核的结果。


压缩比和内存使用

我们还评估了每个文件系统解压缩期间的压缩比和内存消耗。我们同时使用enwik9和silesia。tar表示不同文件系统的压缩比。图9(a)和图9(b)显示了enwik9和silesia.tar在每个文件系统上使用的字节数。两幅图中的原点线表示未压缩工作负载文件的大小,enwik9为953.67MB,silesia.tar为202.1MB。目前,EROFS仅支持4KB大小的输出压缩,但与Squashfs-4K相比,两种工作负载的压缩大小分别小10%和9%。该图还与压缩单元越大,压缩比越好的事实相匹配。
在这里插入图片描述
图10示出了解压缩后使用的存储器enwik9文件。测试如下进行:启动机器,装载文件系统,读取文件系统中存储的文件,检查所用内存,然后重新启动。
在这里插入图片描述
由于文件大约为1GB,剩余部分要么由操作系统的其他部分使用,要么由文件系统临时使用。除了EROFS和Squashfs,我们还测试了Ext4作为基线。从图中,我们可以看出,与Ext4相比,四种Squashfs配置的内存开销在39.6%到61.6%之间。然而,EROFS使用的内存仅略高于Ext4(约4.9%)。结果表明,EROFS具有比Squashfs低得多的内存尖峰,并证明了EROFS的内存友好解压的有效性。

在测试中,只有一个文件要解压缩,我们分配了大量内存,以确保在解压缩过程中不会发生内存回收或交换。然而,在实际场景中,将同时解压缩更多文件,解压缩Squashfs将需要更多内存。一旦可用内存不足,可能会发生内存回收或交换,这非常扩展,不仅影响文件系统,还影响整个系统中的其他组件或应用程序。因此,在现实场景中,EROFS的优势将更加显著,因为它在解压过程中使用的内存很少。


I/O放大和I/O模式

我们对EROFS和不同的壁球场配置重新进行了§2.2中提到的测试。表1列出了在三种读取模式下读取16MB文件数据时发出的实际I/O。EROFS为随机读取和跨距读取发出的I/O最少。然而,由于Squashfs-8K、Squashfs-16K和Squashfs-128K具有更好的压缩比,因此它们比顺序读取的EROFS读取更少的数据。总之,与Squashfs相比,EROFS在大多数情况下减少了I/O放大,特别是对于随机读取和跨距读取。

我们进一步确定了模拟现实环境中的I/O模式,以说明I/O放大将如何影响现实应用程序。我们安装了100个应用程序,运行Monkey工具,每秒随机点击屏幕一次,持续3小时。我们收集了传递给readpage和readpages接口的I/O大小,并在表2中显示了不同I/O尺寸的比例。结果表明,有相当多的I/Os(30.4%)的大小不超过16K,我们将其视为随机I/O。随机I/O的数量是合理的,因为当系统长时间运行时,由于内存不足,应用程序页面缓存中的一些页面会被回收。随机I/O的数量微不足道,这突出了EROFS减少I/O放大的重要性。


吞吐量和空间节省

在这里插入图片描述
图11描述了不同空间节省下EROFS和Ext4的吞吐量。空间节省是压缩减少的空间值除以原始数据的大小;因此,更大的空间节省意味着更多的空间节省。为了简单起见,我们只显示大核心的结果,因为小核心也呈现出类似的趋势。

为了测试,我们在压缩分区中收集块并检查它们的空间节省情况。当我们找到一个与我们预期的空间节省相匹配的块时,原始数据被解压缩并多次复制,形成一个大约512MB的文件。然后我们将文件存储在EROFS中并读取它,以在节省空间的情况下获得读取吞吐量。我们还将文件存储在Ext4中,并获得相应的读取吞吐量进行比较。

通常,在测试期间,Ext4的吞吐量保持稳定,EROFS的性能随着空间节省而提高。当空间节省足够大时,EROFS实现了比Ext4更好的吞吐量。在这种情况下,单个压缩块可以解压缩为几十个块。因此,I/O请求的数量显著减少,从而提高了性能。而当空间节省率较低时,EROFS的性能与Ext4的随机读取相似,而对于顺序读取则不如Ext4。这是I/O成本和解压缩计算成本共同作用的结果。对于随机读取,I/O比解压缩计算更昂贵。而对于顺序读取,由于预取,I/O成本较低,当空间节省较低时,计算成本占主导地位。


不同的解压缩方法和优化

为了说明不同解压方法的效果,我们在Kirin 980上运行FIO顺序读取
具有2600MHz A76核的智能手机。vmap解压方法以726.5MB/s的速度读取文件,而每CPU缓冲区解压的吞吐量为736.5MB/s。在最新的EROFS中,文件数据以769.7MB/s的速度读取,并添加了滚动解压缩和就地解压缩。

我们还评估了§4.3中相同配置下调度优化的效果。在随机读取工作负载中,未进行调度优化的EROFS的平均吞吐量为64.49MB/s,而经过优化后,性能提高了9.5%,达到70.61MB/s。


真实世界应用程序

对于现实世界的应用程序,我们在低端智能手机和高端智能手机上运行了改进的Android 9 Pie,其硬件配置在§5.1中列出。Android系统分区,如/system、/vendor和/odm,使用EROFS压缩,空间节省率从30%到35%。我们测试了生产团队所需的13个流行应用程序的启动时间。我们比较了EROFS和Ext4上的应用程序启动时间,并在表3中显示了相对启动时间。与Ext4相比,EROFS平均将低端智能手机的启动时间减少5.0%,高端智能手机的引导时间减少2.3%。

我们还进行了相同的测试,同时将FIO作为后台工作负载运行,以模拟真实场景。在FIO工作负载中,四个线程随机读写单个文件,读写速率限制为256KB/s。表3中的最后两行显示了FIO工作负载的启动时间,其中低端和高端智能手机的启动时间分别减少了3.2%和10.9%。
在这里插入图片描述
除了各种应用程序的启动时间之外,我们还测试了上述高端智能手机上摄像头应用程序的引导时间分布。为了模拟内存不足的情况,我们在后台运行了一个程序,该程序持续分配内存并填充垃圾数据。在开始实验之前,我们一直等到程序消耗了系统中的所有zram,并在评估期间保持其运行。在实验中,我们依次启动了几个应用程序,并记录了摄像机启动时的启动时间。我们收集了EROFS和Ext4的每次92个摄像机引导,并在图12中显示了累积分布。在90%以上的引导中,在EROFS上运行的摄像机应用程序比在Ext4上运行的更快,而在EROF上最长的引导时间比在EXP4上更差。我们认为这个结果是可以接受的,因为在大多数情况下,EROFS节省了存储空间,同时减少了启动时间。


Experience over deployment

在这里插入图片描述
EROFS已经部署到数千万部智能手机上。这里,我们报告部署期间的一些经验。优化所有情况,而不仅仅是常见情况。部署新的文件系统来替换现有的文件系统比我们最初想象的要困难得多。原因是像智能手机这样的商业产品需要保留现有文件系统的优势和功能。因此,我们需要仔细优化所有情况下的EROF,而不是普通情况下,以避免性能下降,即使在某些罕见情况下也是如此。

例如,在EROFS上读取某些文件的性能比在Ext4上稍差。为了优化,我们将低压缩比的文件保留为未压缩,以获得更好的性能。此外,我们从匿名测试版用户收集文件块的访问频率,并将其存储在专用文件中。根据频率信息,我们对压缩文件中最频繁请求的部分进行预解压缩,并将其固定在内存中,以平衡存储消耗和性能。因此,EROFS的性能可以与Ext4一样好,有时甚至比Ext4更好,同时显著降低了存储消耗。

在现实场景中,不完整的实现会导致性能异常和故障。在主要功能实现后,我们测试了EROFS。然而,在实际测试中发生了几种故障。

一个例子是,智能手机在EROFS上运行了几天后,一些应用程序有时会变得非常慢。最后,我们发现根本原因是EROFS中缺少页面迁移的实现。页面迁移由内存管理子系统调用,以要求文件系统将其数据移动到其他地方。大多数情况下,页面迁移不会被触发,因此不执行页面迁移是良性的。然而,当内存碎片化时,即发生错误时,页面迁移的功能对于在系统中成功分配连续内存至关重要。在我们在EROFS中实现页面迁移后,这个问题得到了解决。瓶颈在不同的平台上转移。我们在资源丰富的高端智能手机上开发了早期版本的EROFS,并将其调整为使用尽可能少的资源。然而,当我们将经调整的EROFS应用于低端智能手机时,性能低于我们的预期。这是令人惊讶的,因为我们已经考虑到有限的资源和EROF应该很好地工作。最后,麻烦制造者原来是调度员。低端智能手机上的调度成本要高得多,并成为EROFS解压的瓶颈,这促使我们引入§4.3中描述的调度优化。不同的平台不仅直接反映了可用内存量或处理器运行速度的资源限制,还可以转移软件瓶颈。


相关工作

其他压缩文件系统。其他几个文件系统支持压缩。AXFS是一个压缩只读文件系统。它旨在启用就地执行(XIP),这是eMMC或UFS等普通智能手机存储不支持的。CramFS是另一种压缩只读文件系统,设计简单,节省空间。但是,它也有一些限制,例如文件大小有限。Cramfs曾经在Linux内核中被Squashfs淘汰,然后在XIP中重新出现,这是eMMC或UFS等普通智能手机存储不支持的。LeCramFS扩展了CramFS,以提高闪存的读取性能和存储效率。然而,压缩比降低,并且LeCramFS生成更大的图像。

JFFS2和UBIF是为闪存设计的两个文件系统。尽管它们支持压缩,但它们必须管理NAND闪存的磨损均衡、地址转换和垃圾收集。因为所有这些功能都已经由eMMC和UFS固件提供,所以EROFS比JFFS2和UBIF简单得多,速度也快得多。

Bcachefs是一个强调可靠性和健壮性的文件系统。ZFS是Sun Microsystems为Solaris设计的成熟文件系统。虽然它们都支持压缩,但它们必须考虑对压缩文件的更新,这与Btrfs有类似的问题。

智能手机的文件系统和存储。智能手机的文件系统和存储一直是热门话题。例如,Kim等人说明了存储可以影响智能手机上的应用程序性能,并提出了几种缓解性能影响的方法。Jeong等人通过对智能手机上的文件系统进行全面检查,发现并缓解了日志记录(JOJ)异常现象,这产生了一些后续工作。他们进一步确定了智能手机文件系统中的准异步I/O,并提高了响应能力。SmartIO通过将读优先于写来减少应用程序延迟。MobiFS是一种以内存为中心的智能手机数据存储设计,可提高响应时间和能耗。

在提供基准框架以评估智能手机上的文件系统和存储方面也做了大量工作。由于非易失性存储器(NVM)的出现,最近的研究人员还研究了如何在智能手机上使用NVM。


结论和未来工作

我们介绍EROFS,一种新的压缩只读文件系统,专为资源有限的智能手机设计。与Squashfs相比,EROFS提供了相当的压缩比,同时具有更高的性能和更少的额外内存开销。通过固定大小的输出压缩和快速高效的内存解压,EROFS可以以更少的存储使用率存储系统代码和资源,有时甚至比不支持压缩的文件系统性能更好。评估表明,安装在EROFS上的系统上的应用程序可以与Ext4上的应用相比,甚至更快地启动。EROFS已被合并到主线Linux,并已在数千万台智能手机上部署和使用。目前,EROFS仍在积极开发中,我们正在不断添加新功能,如重复数据消除、扩展文件统计、fiemap和EROFS融合。


致谢

我们感谢谢泼德·里克·惠勒(shepherd Ric Wheeler)和匿名评论员的建设性意见,感谢李桂福(Guifu Li)帮助mkfs实用程序原型,感谢孙秋阳(Qiuyang Sun)在早期测试和评估中的帮助。陈海波是通讯作者。


评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值