littlefs-project / littlefs

A little fail-safe filesystem designed for microcontrollers
BSD 3-Clause "New" or "Revised" License
5.22k stars 799 forks source link

How to get best performance from LittleFS #1033

Open ashiroji opened 3 weeks ago

ashiroji commented 3 weeks ago

Hello, In the current project, I'am using LittleFS (through Zephyr OS) with an external flash memory to store sensor data in a specific file format. I am using the following Flash memory GD25LB256 (256Mbits) configured in SPI (it supports QSPI too and the HW connections are already made)

I would like to be able to buffer 1 minute of data (29,29Kbytes) and then store it in the flash. The file format used contains an ASCII header (the first 512 bytes) and data. Every time the file is appended, the header must be updated to maintain consistency. The typical scenario is following:

  1. At start, create file with header + 0 data
  2. Every minute, update file with 1 minute of data and update header accordingly.

At the moment, we identify 3 operations:

Unit testing each function independently gives reasonable execution time:

  | file write | file read | file seek | file open | file close -- | -- | -- | -- | -- | -- header creation | 18ms | N/A | N/A | 1ms | 0.6ms data append | 330ms | N/A | N/A | 1.2ms | 1ms header update | 16ms | 0.8ms | 9us | 1ms | 0.6ms

But when combining all functions together, the header update operation execution time grows exponentially.

  | 1 | 2 | 3 | 4 | 5 -- | -- | -- | -- | -- | -- fs_open (ms) | 0,96625 | 1,131437 | 1,296406 | 1,461515 | 1,626156 fs_read (ms) | 1,065453 | 1,065578 | 1,149046 | 0,981859 | 1,065375 fs_seek (us) | 9,281 | 9,281 | 9,281 | 9,281 | 9,281 fs_write (ms) | 15,574031 | 16,230734 | 16,623593 | 17,148656 | 17,585843 fs_close (s) | 1,065068656 | 1,621073343 | 2,176495781 | 2,722240796 | 3,291354078

I suspect that the increase in time is related to the increase in the file size and to some mechanism in LittleFS itself but I'm not very familiar with it.

I've read through a lot of the issues online (especially here) and I'm not sure if my problem is due to a bad configuration or that I'm hitting a bottleneck in LittleFS implementation.

If you can offer some expertise and architectural advice to handle this in a better way that would be appreciated. It would also be helpful, if you can show me how to use LittleFS with a simulated flash

Thank you for your time and help

geky commented 2 weeks ago

Hi @ashiroji, thanks for creating an issue.

I suspect you're running into the same issue as in https://github.com/littlefs-project/littlefs/issues/27, that is littlefs doesn't really implement random writes efficiently. Writes to the beginning of files (such as headers) will cause the entire file to be rewritten.

It's interesting to note you wouldn't have seen this with the separate unit tests as you need both the data and header updates.

This is a known issue, and there's work ongoing to improve this, but it's also a pretty significant/long-term change (rip out and replace the core file data-structure).


As for workarounds, the best option is probably to store the header and data as separate files, and concatenate later if you need to match a specific file format.

Depending on how you transfer files from the device, you could even do this concatenation transparently to the recipient of the file.


It would also be helpful, if you can show me how to use LittleFS with a simulated flash

There are quite a few options here.

One option is to start with lfs_filebd, which maps littlefs's block device API onto a local file, modifying/extending as necessary.

Another option is to fork littlefs's test/bench runner, which is what littlefs uses to run its test suite locally, simulate powerloss, etc. It's powerful but also very strictly provided "as is", has little/no documentation, susceptible to change, etc.

The best way to get started with the test/bench runner would probably be to look at some of the existing tests (tests/test_dirs.toml for example), run the tests, and poke around the test runner to see how it works:

$ make test - j
$ make test-runner -j && ./scripts/test.py runners/test_runner test_dirs
$ ./scripts/test.py --help

It would be nice to document this part of the build system better, but right now it's been low priority.

ashiroji commented 1 week ago

Thank you very much for your help. I'll make some tests based on your workaround idea (separate files) and see what works best in my project.