1 配置分析
参数 | 说明 |
Read_size | 从flash中读取数据的最小size,并且起始地址需要按照这个size对齐 |
prog_size | 往flash中写入数据的最小size,并且起始地址需要按照这个size对齐。 |
block_size | 文件系统中物理block的size。 |
block_count | 文件系统中物理block的个数。 |
block_cycles | 元数据块擦写均衡的次数,当block完成block_cycles次擦写操作之后,将重新申请新的block,源数据导入到新的block,继续擦写操作。 |
cache_size | 文件系统的读写时,数据在内存中的缓存大小,文件系统初始化的时候在内存中申请了一个写cache和读cache,open文件的时候还申请了一个文件cache,他们都是cache_size大小。 |
lookahead_size | 空闲块滑窗的大小,littlefs用一个滑窗来管理所有的空闲块,并且用一个bit是否为0来代表对应的物理block是否空闲,当空闲块不够的时候,会进行滑动操作,一次滑动lookahead_size,并且遍历整个文件系统,判断物理block是否被使用,从而给滑窗的bit状态置位正确的值。 |
2 性能分析
2.1 环境信息
2.1.1硬件信息
通过使用rthread的fal bench命令,对手头的板子(cpu主频160MHz、SPI时钟主频20MHz)进行flash的性能测试,得出的结论如下:
操作类型 | 大小(字节) | 时间(ms) |
擦除 | 4096 | 22 |
写 | 4096 | 20 |
读 | 4096 | 4 |
2.1.2 littlefs配置信息
参数 | 大小 |
Read_size | 256 |
prog_size | 256 |
block_size | 4096 |
block_count | 1792 |
block_cycles | 10 |
cache_size | 4096 |
lookahead_size | 128 |
2.1.3 测试用例说明
- 使用rtthread操作系统加载littlefs
- 操作系统的systick配置为5ms
- 一次写操作的大小为128字节
- 顺序写的文件大小为32kByte
- 覆盖写的文件大小为8192Byte
- 操作的文件是根目录下的文件,且仅有此文件
- 操作之前,先格式化一把文件系统,减少历史碎片的干扰
2.2 顺序写性能测试
2.2.1 写性能分析
从测试可以看出来,顺序写分为两个阶段:
- 写入到元数据内部,时间开销比较小;
- 写入到专门的物理block里面,时间开销主要在于write file的erase flash操作以及close file的write flash操作。
2.2.2 flash操作分析
从上面的截图可以看到,每隔16个包,就会有一个比较大的时间开销,这个时候其实是文件系统进行了一次compact操作,此时的性能很差,我们把flash的相关日志打印出来,分析性能消耗在哪里:
从测试可以看出来,compact操作的时间开销主要在于close的时候,其执行了erase flash一次、write flash一个block size,read flash 近50k数据,时间开销差不多分别为22ms、20ms、51ms,接近90ms。
2.3 覆盖写性能分析
2.3.1 写性能分析
从测试可以看出来:
- write的时候,读取一个块的数据,擦一个块的数据,写当前块offset大小的数据到对应的pcache里面,write的时间在变大,因为offset在变大,每次在write的时候,需要读取的数据变大了;
- close的时候,擦两个块的数据,读剩下的完整的数据,写剩下的完整的数据,close的时间在变小,因为write过程中读取的数据变多了,那么close的时候要读取的数据就变少了。
2.3.2 flash操作分析
覆盖写的性能,异常的差,把flash的操作打印打开(因为耗时最长的地方肯定是对flash的操作),深入分析一次完整的写入流程,对flash有哪些操作:
close的时间达到了170ms,和顺序写相比,高了很多倍。其主要的耗时在:2次erase操作,3次write 4096字节的操作, 1次write 256字节的操作,多次read操作,读取了近30k的数据。时间计算为: 22*2 + 20*3 + 20/16 + 8 *4 = 137ms,线程切换以及其他的运算操作有接近30ms了?
2.4 性能总结
2.4.1写抖动
不论和顺序写还是覆盖写,存在抖动,抖动的时间开销主要在于:
- 元数据块的压缩过程,读取flash的次数太多;
- 压缩不了后,进行分片,有多余的擦除操作和压缩操作;
- block_cycles达到上限之后,会申请新的元数据对,并有压缩的过程
2.4.2 覆盖写性能差
覆盖写的时候,文件越大,写的位置越前面,性能越差。
3 使用注意事项
3.1 配置注意事项
- read_size和prog_size不要太大了,不然会频繁的触发压缩过程。
- 在保证内存的情况下,lookahead_size越大越好,因为lookahead窗口的建立过程消耗的时间,不受lookahead_size的影响。lookahead窗口用1bit代表1个block size,其实对内存的开销也还好。
(3)block_cycles越小,擦写均衡的效果越好,但是写入抖动的频率会越高。
(4)cache_size一般和block size对齐就好了。
3.2 实际使用注意事项
- 不要同时open太多文件,每open一个文件,没有close的情况下,会增加cache_size大小的内存开销。
- write完数据要尽快的close,因为只有close操作完成了,异常掉电后,write的数据才能被文件系统正确检索。
- 一个目录下面不要放太多的文件,不然压缩的过程(write的抖动)时间会比较大。
- 文件的大小尽可能小于1个block size,如果大于1个block size,那么尽可能按照ctz特性来设计文件大小,不然可能性能比想象的差,例如文件大小为8192(占据三个ctz block),比文件大小为8000(占据两个ctz block),要差很多。
(5)对写时延要求比较高的文件,尽量放到一级目录,写时间会短一些。
历史存档
08-08 01:50:15 D/lfs_test lfs_test: -----start open----
读取两个pair block的resv,选择大的那个,结果选择了1199,然后把tag全部读取出来
08-08 01:50:15 I/NO_TAG lfs_srv: read 1198 256
08-08 01:50:15 I/NO_TAG lfs_srv: read 1199 256
08-08 01:50:15 I/NO_TAG lfs_srv: read 1199 3840
08-08 01:50:15 D/lfs_test lfs_test: -----start lseek----
lseek啥都没干
08-08 01:50:15 D/lfs_test lfs_test: -----start write----
找到偏移对应的ctz block,擦除一个新的块,读取偏移大小的数据,写入到新的块对应的pcache里面
08-08 01:50:15 I/NO_TAG lfs_srv: read 1396 256
08-08 01:50:15 I/NO_TAG lfs_srv: erase 1397
08-08 01:50:15 I/NO_TAG lfs_srv: read 1394 1280
08-08 01:50:15 D/lfs_test lfs_test: -----start close----
再次找到偏移对应的ctz block,把剩余的2816字节读取了,写入到1397对应的block,写完之后,再全部读取出来,校验一把
08-08 01:50:15 I/NO_TAG lfs_srv: read 1396 256
08-08 01:50:15 I/NO_TAG lfs_srv: read 1394 2816
08-08 01:50:15 I/NO_TAG lfs_srv: write 1397 0
08-08 01:50:15 I/NO_TAG lfs_srv: read 1397 4096
pos偏移到下一个block了,找到pos对应的ctz block,把整个block全部读取出来,擦除一个新的块1398,然后把数据写入1398,再全部读取出来,校验一把
08-08 01:50:15 I/NO_TAG lfs_srv: read 1396 256
08-08 01:50:15 I/NO_TAG lfs_srv: read 1395 4096
08-08 01:50:15 I/NO_TAG lfs_srv: erase 1398
08-08 01:50:15 I/NO_TAG lfs_srv: write 1398 0
08-08 01:50:15 I/NO_TAG lfs_srv: read 1398 4096
最后一个块的位置(ctz的链表头部),本来就确定了的,在open的时候就有了,把整个block全部读取出来,擦除一个新的块1399,然后把数据写入1399,再全部读取出来,校验一把(校验只校验了256个字节,因为实际关注的只有这么多数据)
08-08 01:50:15 I/NO_TAG lfs_srv: read 1396 4096
08-08 01:50:15 I/NO_TAG lfs_srv: erase 1399
08-08 01:50:15 I/NO_TAG lfs_srv: read 1398 256
08-08 01:50:15 I/NO_TAG lfs_srv: read 1396 4096
08-08 01:50:15 I/NO_TAG lfs_srv: write 1399 0
08-08 01:50:15 I/NO_TAG lfs_srv: read 1399 256
提交新的ctz tag信息到元数据块,并且写完之后再校验一把,本次写入了256字节
08-08 01:50:15 I/NO_TAG lfs_srv: start commit
08-08 01:50:15 I/NO_TAG lfs_srv: read 1199 256
08-08 01:50:15 I/NO_TAG lfs_srv: start sync
08-08 01:50:15 I/NO_TAG lfs_srv: write 1199 1536
08-08 01:50:15 I/NO_TAG lfs_srv: read 1199 256
08-08 01:50:15 D/lfs_test lfs_test: file_system_info: cover_write offset 1280 cost 10 0 35 195 ms
close的时间达到了170ms,和顺序写相比,高了很多倍。其主要的耗时在:2次erase操作,3次write 4096字节的操作, 1次write 256字节的操作,多次read操作,读取了近30k的数据。时间计算为: 22*2 + 20*3 + 20/16 + 8 *4 = 137ms,线程切换以及其他的运算操作有接近30ms了?