littlefs性能分析
分析的目的很简单:希望支持掉电安全,或者说具有奔溃一致性特性的文件系统,他的读写速度能得到提升。如果了解了瓶颈所在,也可触类旁通。
本次分析,使用了大量的对比测试:
littlefs | 读(KB/S) | 写(KB/S) | 分析 |
---|---|---|---|
lfs初始速度 read_size=512, prog_size=512, block_size=512, lookahead_size=8 | 0.765178101 | 18.980361328 | 初始速度 |
lookahead_size=256 | 18.695521484 | 18.400935547 | ⬆ |
更新littlefs到最新版本,lookahead_size=256 | 18.781650391 | 18.402720703 | - |
lookahead_size=8192 | 34.649105469 | 18.399744141 | ⬆ |
block_cycles从-1改为1000 | 34.795378906 | 18.393941406 | - |
lookahead_size=8k ,block_size=120k=240sector | 3729.2665 | 3441.53175 | 需要同步更改lfs_io文件 |
lookahead_size=8k,block_size=1024sector | 5603.085 | 7898.793 | ⬆ |
取消fal | 9439.222 | 7844.319 | 取消fal对读速度影响很大,对写没有影响 |
直接使用块驱动接口,不使用驱动框架 | 9323.166 | 7871.461 | - |
更改测试用例 | 11524.075 | 8597.326 | ⬆ |
read_size=128sector, prog_size=1024sector, block_size=1024sector | 13287.461 | 19012.166 | 减小了readsize |
一次性写1024block | 26.10 MB/s | 28.44 MB/s | 直接调用驱动接口os_device_write_nonblock |
一次性写4block | 0.25MB/s | 0.27MB/s | - |
为何驱动接口一次性读取更多的block会更快?
在eMMC存储中,一次性读取更多的数据块可能会更快的因素包括:
- 减少命令开销:每次读取操作之前,主机需要向eMMC发送命令。如果一次性读取更多的数据块,可以减少命令的发送次数,从而降低命令传输的开销。
- 提高数据传输带宽利用率:eMMC接口支持高速数据传输,一次性读取更多的数据块可以更充分地利用接口的带宽,从而提高数据传输的效率。
- 利用eMMC的缓存:eMMC设备内部通常包含一定大小的缓存,用于优化数据传输。一次性读取更多的数据块可以更有效地利用这个缓存,减少访问实际闪存介质的次数。
- 减少事务处理次数:eMMC的读写操作是通过事务来管理的,每个事务包括命令、响应和数据传输。减少事务处理的次数可以提高整体的传输效率。
- 优化闪存操作:eMMC使用的是闪存存储技术,闪存的读取操作在处理大量数据时更为高效。一次性读取更多的数据块可以减少对闪存的编程/擦除操作,从而提高读取速度。
- 减少接口切换:eMMC接口支持多种操作模式,包括数据传输模式和命令模式。一次性读取更多的数据块可以减少在这些模式之间切换的次数,从而节省时间。
为何lfs的读写操作要比直接调用驱动接口更慢?
- 抽象层开销:LFS作为文件系统,提供了文件和目录的抽象。这意味着在进行读写操作时,LFS需要处理文件系统的元数据,如目录结构、文件属性等,这增加了额外的开销。
- 缓存和缓冲:文件系统通常会使用缓存来优化对存储设备的访问。LFS可能有自己的缓存策略,这可能会增加读写操作的复杂性,尤其是在缓存失效或需要刷新缓存到存储设备时。
- 事务日志:LFS可能使用事务日志来确保文件系统的原子性和一致性。这意味着每次写操作可能需要额外的步骤来记录日志,这会增加写操作的延迟。
- 额外的写入操作:为了维护文件系统的结构,LFS可能需要进行额外的写入操作,比如更新文件系统的分配位图或者索引数据结构。
- 碎片处理:文件系统可能会随着时间的推移产生碎片,这需要LFS进行额外的管理工作,如动态地分配和回收存储空间,这可能会导致读写操作变慢。
- 同步和异步操作:直接调用驱动接口的读写操作可能更直接地控制同步和异步行为。而文件系统可能会为了数据的完整性而默认使用同步操作,这会阻塞直到数据完全写入或读取。
- 文件系统的一致性检查:LFS可能需要在启动或特定操作时执行一致性检查,以确保文件系统的完整性,这可能会导致额外的延迟。
- 系统调用开销:使用文件系统通常涉及到系统调用,这比直接在用户空间操作硬件驱动接口要慢,因为系统调用涉及到用户空间与内核空间之间的上下文切换。
总的来说,LFS提供了丰富的文件管理功能,但这些功能在提供便利的同时也带来了额外的性能开销。对于需要极高读写性能的应用,直接操作存储设备驱动接口可能会更高效,但这通常需要更复杂的错误处理和设备管理逻辑
为何fal让写速率变慢?
在接口fal_blk_write_block
中调用了fal_part_erase
擦除接口,他会执行擦除操作,把所有的位都置1。
但其实对于eMMC存储设备,开发者通常不需要手动调用擦除接口。eMMC设备的控制器已经对底层的闪存操作进行了抽象和封装,包括必要的擦除操作。这意味着当应用程序写入数据到eMMC时,控制器会自动处理闪存块的管理,包括在必要时擦除和重写数据。
为何提高block_size,read_size,prog_size会让性能提升?
修改它们的同时,需要同步修改lfs_io.c
文件,之间的lfs_flash_write/lfs_flash_read
只能一次写入一个block,导致性能较弱。提高size
大小,最终对应到os_device_write_nonblock
,可以让驱动接口一次性读写多个块,从而提升性能。
直接从存储介质读取:如果数据既不在预缓存也不在读缓存中,且满足一定条件(如:大小大于等于缓存提示值并且读取位置对齐),则跳过缓存,直接从存储介质读取数据到用户缓冲区。
选择数据块:如果当前文件不在读取模式或当前块已读完,函数会找到包含文件当前位置的数据块。对于非内联文件,使用lfs_ctz_find
查找数据块。
加载数据到读缓存:如果上述条件不满足,将数据加载到读缓存中,确保读取位置对齐,并读取尽可能多的数据,但不超过缓存大小和块大小。
为何提高littlefs的lookahead_size,能让性能提升?
- 减少查找开销:当LittleFS需要分配新的存储块时,它会使用
lookahead buffer
来预先检查和标记一系列可用块。一个更大的lookahead buffer
意味着可以预先检查更多的块,从而减少在实际写入数据时寻找可用块所需的时间和开销。这在连续分配多个块时特别有效,因为它减少了对闪存的重复扫描。 - 优化块分配策略:由于
lookahead buffer
是按位图形式组织的,更大的尺寸意味着能够覆盖更多的物理块状态。这样,文件系统可以在更宽的范围内进行块分配选择,有助于实现更好的磨损平衡,避免某些块过早达到擦写极限,从而延长存储介质的寿命。 - 减少写入放大:通过更有效的块分配,可以减少不必要的擦除和重写操作(写入放大),因为文件系统可以更好地规划数据的存放位置,避免频繁地移动数据来腾出空间。这对于基于闪存的存储介质(如eMMC、NAND Flash)尤为重要,因为这些介质的擦写操作相比读取操作要慢得多且寿命有限。
- 提高顺序写入速度:对于连续的大块数据写入,较大的
lookahead_size
可以确保文件系统有足够多的连续空闲块来满足需求,从而减少碎片化,提高顺序写入的速度。