SquashFS 是 Linux 的只读压缩文件系统。该文件系统设计为只读属性,因此适合在系统分区上使用。很多 Android 设备都可以通过对其系统分区使用此文件系统来获益,例如以下设备:
- 存储容量小的设备,例如 Android Watch。
- 闪存速度缓慢的设备(压缩可减少块 I/O 的数量)。
遗憾的是,SquashFS 的性能落后于 ext4。
优化
为提高 SquashFS 的性能,已经实现下列优化。
减少内存使用量和 memcpy 调用次数
读取块(默认为 128K)时,SquashFS 会尝试抓取包含此块的所有页面。
如果某个页面是最新页面或已被锁定,SquashFS 会转而分配一个完整块,提交读取请求,然后将其内容复制到这些页面。
这种方法极其低效;经过一段时间后,页面缓存可能包含最新页面,即使相邻页面并非最新页面也是如此。
代码现在能够处理有孔(即缺少页面)的块,因而可以通过以下方式来提高性能:
- 减少
memcpy
调用次数 - 减少内存分配
异步读取
SquashFS 仍使用已弃用的 ll_rw_block()
函数。使用这种方法存在两个问题:
- 顾名思义,该函数会等待读取完成之后再返回结果。这是多余的,因为
.readpage()
已在页面锁定上等待。此外,我们需要一个异步机制来高效实现.readpages()
。 - 合并读取请求完全取决于 I/O 调度程序。
ll_rw_block()
只会为每个缓冲区创建一个请求。在应该合并的内容方面,SquashFS 包含的信息比 I/O 调度程序多。此外,合并请求意味着我们对 I/O 调度程序的依赖会有所减少。
因此,ll_rw_block()
函数已被 submit_bio()
替换。
Readpages(预提取)
SquashFS 不会实现 .readpages()
,因此内核会反复调用 .readpage()
。
由于我们的读取请求是异步请求,因此内核可以使用其异步预读机制真正地预提取页面。
优化未压缩块的读取操作
诸如 Android 之类的现代系统包含大量经过压缩的文件。因此,映像包含大量无法压缩的块。
SquashFS 使用相同的逻辑处理经过压缩的块和未压缩的块:当 SquashFS 需要读取一个页面时,它实际上读取的是一个完整块(默认为 128k)。虽然对于经过压缩的块,这是必需的;但对于未压缩的块,这只是在浪费资源。
现在,SquashFS 只读取预读算法建议的内容,而不会读取一个完整块。
这极大地提高了随机读取的性能。
代码
AOSP 中提供 SquashFS 代码优化: