Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Will esp_littlefs support large capacity TF Card? #211

Closed
yiming564 opened this issue Jan 5, 2025 · 23 comments
Closed

Will esp_littlefs support large capacity TF Card? #211

yiming564 opened this issue Jan 5, 2025 · 23 comments

Comments

@yiming564
Copy link

LittleFS can support large partition, but it seems that esp_littlefs limited TF Card's partition size to 0xffffffff, because ESP32 is a 32-bit platform and SIZE_MAX is 0xffffffff.

Will esp_littlefs remove the limit and support large partition?

@yiming564
Copy link
Author

In esp_littlefs.c:936:

        size_t max_allowed_blk_cnt = 0;
        if (((uint64_t)sdcard->csd.capacity * (uint64_t)sdcard->csd.sector_size) > SIZE_MAX) {
            max_allowed_blk_cnt = SIZE_MAX / sdcard->csd.sector_size;
            ESP_LOGW(ESP_LITTLEFS_TAG, "SD card is too big (sector=%d, count=%d; total=%llu bytes), throttling to maximum possible %u blocks",
                     sdcard->csd.sector_size, sdcard->csd.capacity,
                     (((uint64_t)sdcard->csd.capacity * (uint64_t)sdcard->csd.sector_size)), max_allowed_blk_cnt);
        } else {
            max_allowed_blk_cnt = sdcard->csd.capacity;
        }

here esp_littlefs limit partition max size to SIZE_MAX

@BrianPugh
Copy link
Member

@huming2207 what was the reason for this check? I'm not too familiar with the sdmmc interface. A naive skim of littlefs_sdmmc.c indicates that sdmmc doesn't use the partition api. We obviously cannot support a fully 64bit address space, but it looks like we could make it a bit larger. lfs_block_t is a uint32_t, and if block_size is 4096 (12-bits), then we can support 44-bits (16TB), which is certainly beyond a reasonable limit for an esp-device.

@huming2207
Copy link
Collaborator

Sorry for the late reply. Yes I think the SD card driver itself should be able to support 16TB. I did ask the ESP-IDF maintainer before. I will try if I can find that discussion thread later.

@huming2207
Copy link
Collaborator

See:

And yes, I think I should fix this logic:

        if (((uint64_t)sdcard->csd.capacity * (uint64_t)sdcard->csd.sector_size) > SIZE_MAX) {
            max_allowed_blk_cnt = SIZE_MAX / sdcard->csd.sector_size;
            ESP_LOGW(ESP_LITTLEFS_TAG, "SD card is too big (sector=%d, count=%d; total=%llu bytes), throttling to maximum possible %u blocks",
                     sdcard->csd.sector_size, sdcard->csd.capacity,
                     (((uint64_t)sdcard->csd.capacity * (uint64_t)sdcard->csd.sector_size)), max_allowed_blk_cnt);
        } else {
            max_allowed_blk_cnt = sdcard->csd.capacity;
        }

It should be only reject to work if either the SD card sector count and sector size is bigger than UINT32_MAX (as per discussed here: https://github.com/littlefs-project/littlefs/blob/master/SPEC.md#0x0ff-lfs_type_superblock

I will fix this later tonight if I can. But so far I don't have hardware set up at the moment as I'm busy working on something else, so I can't test it.

@huming2207
Copy link
Collaborator

@yiming564 if you want, please try my branch here: 15846f4

Please test throughly and report if that works or not. I don't have test hardware and I don't have time to test at the moment. 😅

@yiming564
Copy link
Author

@huming2207 I have tested your program and SD Card can be successfully mounted, and I also wrote a simple program to test whether LittleFS can really support large partition:

...

// Only a part of my code.
#include <sys/stat.h>
#include <fcntl.h>
#include <memory.h>

static const size_t blk = 0x10000, blk_cnt = 1 << 17; // 8 GiB

#define TEST_PATH	(EXT_PATH "/BigFile.txt")

esp_err_t stress_test()
{
	ESP_LOGI(app_tag, "Start stress test.");
	int fd = open(TEST_PATH, O_WRONLY | O_CREAT, 0777);
	APP_CHECK_WITH_LOG(fd >= 0, ESP_ERR_NOT_FOUND, app_tag, "Cannot create %s", TEST_PATH);
	uint8_t *buf = malloc(blk);
	memset(buf, 'A', blk);

	for (int i = 0; i < blk_cnt; i++)
	{
		if ((i & 31) == 0) ESP_LOGI(app_tag, "Written %u/%u blk.", i, blk_cnt);
		APP_CHECK_WITH_LOG(write(fd, buf, blk) != -1, ESP_FAIL, app_tag, "Write failed!");
	}
	ESP_LOGI(app_tag, "Completed!");
	close(fd), free(buf);

	struct stat st;
	stat(TEST_PATH, &st);
	ESP_LOGI(app_tag, "File size check: %lu Bytes", st.st_size);
	return ESP_OK;
}

...

This program tried to create a 8 GiB file and fill it.

I tested the program on my own hardware, and found the writing speed is unexpectedly slow:

...
I (00:00:10.235) app_manager: Start stress test.
I (00:00:10.266) app_manager: Written 0/131072 blk.
I (00:00:52.456) app_manager: Written 32/131072 blk.
I (00:01:35.005) app_manager: Written 64/131072 blk.
I (00:02:17.935) app_manager: Written 96/131072 blk.
I (00:03:01.245) app_manager: Written 128/131072 blk.
I (00:03:44.925) app_manager: Written 160/131072 blk.
I (00:04:29.275) app_manager: Written 192/131072 blk.
I (00:05:13.725) app_manager: Written 224/131072 blk.
...

About 48 KiB/s, that's pretty slow. It may take days to fill the file.

@yiming564
Copy link
Author

P.S. sdmmc is configured as 4-line, 40 mhz.

@huming2207
Copy link
Collaborator

Thanks for the report! @yiming564

About 48 KiB/s, that's pretty slow. It may take days to fill the file.

What about bigger blocks? In your code the blk is 0x10000, how about double that? Will that be quicker?

Also have you tried increasing the cache size?

I think this speed somewhat make sense. From our impression earlier we tested with the XTX SD NAND, it was also very slow. We were trying to implement a time series database to store our log, so the latency and speed is critical for us. But LittleFS (and/or ESP-VFS) was too slow. We then moved to our own lower-level filesystem implementation instead.

@yiming564
Copy link
Author

@huming2207 thank you for the reply.

Also have you tried increasing the cache size?

emm I don't suppose write() use cache, so cache size may have no effect on the writing speed.

and I think the theoretical transmission speed limit is $\frac{4 \times 40}{8} = 20$ MiB/s, and my sd card's sequential r/w speed is much more quicker than that. So it's strange that the real r/w speed is only 48 KiB/s.

@huming2207
Copy link
Collaborator

emm I don't suppose write() use cache, so cache size may have no effect on the writing speed.

and I think the theoretical transmission speed limit is 4 × 40 8 = 20 MiB/s, and my sd card's sequential r/w speed is much more quicker than that. So it's strange that the real r/w speed is only 48 KiB/s.

hmmm yea...

have you tested the raw SD card write speed (just by calling the SDMMC APIs)? @yiming564

@BrianPugh
Copy link
Member

BrianPugh commented Jan 10, 2025

regardless of performance, I think we should merge in #212. If there are performance optimizations we can perform in this repo, we can follow it up with another PR.

#212 has been merged and is now part of the 1.16.2 release.

@huming2207
Copy link
Collaborator

If there are performance optimizations we can perform in this repo, we can follow it up with another PR.

Yep. But I think this is a bug somewhere I need to find out.

For FATFS on ESP-IDF with SD card, people are getting MB/s level speed, see:

Meanwhile, another question for @yiming564 , did you enable CONFIG_LITTLEFS_FLUSH_FILE_EVERY_WRITE?

@huming2207
Copy link
Collaborator

Also @yiming564 can you please try running this: https://github.com/espressif/esp-idf/tree/v5.4/examples/storage/perf_benchmark

I'm also trying to set up an enviroment to test out. So far I only have one spare SD card at home that allows me to do random experiments. It's horrible but it still works.

On the card reader of my Kensington Thunderbolt3 dock, it gets this speed by running sudo dd if=/dev/zero of=/dev/rdisk6s1 bs=1M count=100:

100+0 records in
100+0 records out
104857600 bytes transferred in 8.990285 secs (11663434 bytes/sec)

@huming2207
Copy link
Collaborator

Hmm yea, even with official demo and SDSPI at 20MHz, ESP-IDF v5.4, LittleFS has already lost the game compared to FATFS:

With FATFS:

I (411) example: SD card mounted - FATFS
[SD FATFS (new file): write target size                 ] (10x)   73.093 ms    64.00 kB      0.855 MB/s
[SD FATFS (new file): read target size                  ] (10x)   40.500 ms    64.00 kB      1.543 MB/s
[SD FATFS (new file): write more than target size       ] (10x)   73.249 ms    76.80 kB      1.024 MB/s
[SD FATFS (new file): read more than target size        ] (10x)   50.370 ms    76.80 kB      1.489 MB/s
[SD FATFS (new file): write less than target size       ] (10x)   52.966 ms    53.33 kB      0.983 MB/s
[SD FATFS (new file): read less than target size        ] (10x)   34.410 ms    53.33 kB      1.514 MB/s
[SD FATFS (new file): write tiny size                   ] (10x)    1.002 ms     0.25 kB      0.243 MB/s
[SD FATFS (new file): read tiny size                    ] (10x)    0.800 ms     0.25 kB      0.304 MB/s
[SD FATFS: write target size                            ] (10x)   55.162 ms    64.00 kB      1.133 MB/s
[SD FATFS: read target size                             ] (10x)   40.557 ms    64.00 kB      1.541 MB/s
[SD FATFS: write more than target size                  ] (10x)  222.468 ms    76.80 kB      0.337 MB/s
[SD FATFS: read more than target size                   ] (10x)   95.335 ms    76.80 kB      0.787 MB/s
[SD FATFS: write less than target size                  ] (10x)  172.051 ms    53.33 kB      0.303 MB/s
[SD FATFS: read less than target size                   ] (10x)   66.376 ms    53.33 kB      0.785 MB/s
[SD FATFS: write tiny size                              ] (10x)    1.072 ms     0.25 kB      0.227 MB/s

With LittleFS:

[SD LittleFS (new file): write target size              ] (10x)  579.959 ms    64.00 kB      0.108 MB/s
[SD LittleFS (new file): read target size               ] (10x)  529.950 ms    64.00 kB      0.118 MB/s
[SD LittleFS (new file): write more than target size    ] (10x)  687.210 ms    76.80 kB      0.109 MB/s
[SD LittleFS (new file): read more than target size     ] (10x)  921.831 ms    76.80 kB      0.081 MB/s
[SD LittleFS (new file): write less than target size    ] (10x)  478.973 ms    53.33 kB      0.109 MB/s
[SD LittleFS (new file): read less than target size     ] (10x)  497.753 ms    53.33 kB      0.105 MB/s
[SD LittleFS (new file): write tiny size                ] (10x)    1.424 ms     0.25 kB      0.171 MB/s
[SD LittleFS (new file): read tiny size                 ] (10x)    0.783 ms     0.25 kB      0.311 MB/s
[SD LittleFS: write target size                         ] (10x)  623.023 ms    64.00 kB      0.100 MB/s
[SD LittleFS: read target size                          ] (10x)  829.416 ms    64.00 kB      0.075 MB/s
[SD LittleFS: write more than target size               ] (10x)  723.430 ms    76.80 kB      0.104 MB/s
[SD LittleFS: read more than target size                ] (10x)  983.704 ms    76.80 kB      0.076 MB/s
[SD LittleFS: write less than target size               ] (10x)  491.215 ms    53.33 kB      0.106 MB/s
[SD LittleFS: read less than target size                ] (10x)  871.668 ms    53.33 kB      0.060 MB/s
[SD LittleFS: write tiny size                           ] (10x)    2.195 ms     0.25 kB      0.111 MB/s
[SD LittleFS: read tiny size                            ] (10x)    1.247 ms     0.25 kB      0.195 MB/s

@huming2207
Copy link
Collaborator

With SDMMC 4-lane, it looks a bit better, but I think either I've got a rubbish SD card breakout board from Taobao, or the SD card itself is crap. The benchmark didn't quite work when there's a huge amount of data:

As you see, the FATFS benchmark will always die in the middle, but the performance looks better than SDSPI for sure:

I (422) example: SD card mounted - FATFS
[SD FATFS (new file): write target size                 ] (10x)   31.965 ms    64.00 kB      1.955 MB/s
[SD FATFS (new file): read target size                  ] (10x)    7.114 ms    64.00 kB      8.786 MB/s
[SD FATFS (new file): write more than target size       ] (10x)   18.042 ms    76.80 kB      4.157 MB/s
[SD FATFS (new file): read more than target size        ] (10x)    9.308 ms    76.80 kB      8.057 MB/s
[SD FATFS (new file): write less than target size       ] (10x)   14.323 ms    53.33 kB      3.636 MB/s
[SD FATFS (new file): read less than target size        ] (10x)    6.227 ms    53.33 kB      8.365 MB/s
[SD FATFS (new file): write tiny size                   ] (10x)    0.460 ms     0.25 kB      0.529 MB/s
[SD FATFS (new file): read tiny size                    ] (10x)    0.345 ms     0.25 kB      0.704 MB/s
[SD FATFS: write target size                            ] (10x)   10.181 ms    64.00 kB      6.139 MB/s
[SD FATFS: read target size                             ] (10x)    7.117 ms    64.00 kB      8.782 MB/s
[SD FATFS: write more than target size                  ] (10x)  154.955 ms    76.80 kB      0.484 MB/s
[SD FATFS: read more than target size                   ] (10x)   33.298 ms    76.80 kB      2.252 MB/s
[SD FATFS: write less than target size                  ] (10x)  114.241 ms    53.33 kB      0.456 MB/s
E (12482) sdmmc_req: handle_idle_state_events unhandled: 00000100 00000000
E (12482) sdmmc_cmd: sdmmc_read_sectors_dma: sdmmc_send_cmd returned 0x109, failed to get status (0x107)
E (12492) diskio_sdmmc: sdmmc_read_blocks failed (0x109)
E (12492) fs_test: Could not read full buffer size. Actually read bytes: 172.
read: I/O error
E (12502) sdmmc_cmd: sdmmc_read_sectors_dma: sdmmc_send_cmd returned 0x107, status 0xc00b00
E (12512) diskio_sdmmc: sdmmc_read_blocks failed (0x107)

With LittleFS however it completed the test (because the bottleneck wasn't at the SDMMC connection, probably at the LittleFS side 😅), and the performance is a bit embarrassing:

I (423) example: LittleFS mounted to /sdcard
I (433) example: SD card mounted - LittleFS
[SD LittleFS (new file): write target size              ] (10x) 1306.874 ms    64.00 kB      0.048 MB/s
[SD LittleFS (new file): read target size               ] (10x)  200.811 ms    64.00 kB      0.311 MB/s
[SD LittleFS (new file): write more than target size    ] (10x) 1564.515 ms    76.80 kB      0.048 MB/s
[SD LittleFS (new file): read more than target size     ] (10x)  349.107 ms    76.80 kB      0.215 MB/s
[SD LittleFS (new file): write less than target size    ] (10x) 1093.620 ms    53.33 kB      0.048 MB/s
[SD LittleFS (new file): read less than target size     ] (10x)  189.712 ms    53.33 kB      0.275 MB/s
[SD LittleFS (new file): write tiny size                ] (10x)    8.501 ms     0.25 kB      0.029 MB/s
[SD LittleFS (new file): read tiny size                 ] (10x)    0.309 ms     0.25 kB      0.786 MB/s
[SD LittleFS: write target size                         ] (10x) 1321.863 ms    64.00 kB      0.047 MB/s
[SD LittleFS: read target size                          ] (10x)  314.314 ms    64.00 kB      0.199 MB/s
[SD LittleFS: write more than target size               ] (10x) 1575.891 ms    76.80 kB      0.048 MB/s
[SD LittleFS: read more than target size                ] (10x)  372.393 ms    76.80 kB      0.201 MB/s
[SD LittleFS: write less than target size               ] (10x) 1091.836 ms    53.33 kB      0.048 MB/s
[SD LittleFS: read less than target size                ] (10x)  331.817 ms    53.33 kB      0.157 MB/s
[SD LittleFS: write tiny size                           ] (10x)    5.833 ms     0.25 kB      0.042 MB/s
[SD LittleFS: read tiny size                            ] (10x)    0.485 ms     0.25 kB      0.501 MB/s

@huming2207
Copy link
Collaborator

huming2207 commented Jan 11, 2025

I guess maybe I can't just set block size to the actual size of the SD card sector. SD card is written per sector based not per byte based. So right now I guess what happened is, all the R/Ws are done in each 512 bytes request, not something bigger like FAT (probably 64KB like this case?). These kinds of R/W requests to SD cards are way too fragmented, which explains why it's so slow.

I'm also trying to do something like this, by testing the read/write size and block size to be 2x bigger, but it will cause lockup. I don't know why:

image

@BrianPugh
Copy link
Member

I don't really have much to offer here, but here are some relevant github issues (that don't really offer any additional suggestions):

The general thoughts are that littlefs may not be great for sd cards, but it doesn't seem too well-researched.

@huming2207 I think those changes should work, but sure why they are not. Another (probably inconsequential) thing is that I don't think SD cards benefit from wear-leveling, as they should handle that internally on their own. This can be done by setting cfg.block_cycles = -1;

@huming2207
Copy link
Collaborator

I think those changes should work, but sure why they are not. Another (probably inconsequential) thing is that I don't think SD cards benefit from wear-leveling, as they should handle that internally on their own. This can be done by setting cfg.block_cycles = -1;

Hmm, I don't know either. I think something went wrong with my hardware setup.

I have some CSNP16GCR01-AOW SD NAND chips and breakout boards in my hand now. I should try it out later.

@igrr
Copy link

igrr commented Jan 12, 2025

For SD cards, throughput can be increased if the block size read or written at once is increased. As I understand from esp_littlefs code, there are two issues:

  1. The block size is fixed to 4 kB. It's a reasonable setting, however for higher throughput applications it would be nice to be able to increase it.
  2. When the application writes a block larger than 4 kB at once, the filesystem implementation still internally splits it into several 4 kB writes. By comparison, FATFS can allocate a sequence of sectors and write them to the card at once.

(This is based on a cursory look at the code, apologies if I got something wrong.)

@huming2207
Copy link
Collaborator

huming2207 commented Jan 12, 2025

Hi @igrr , thanks for the reply.

  1. The block size is fixed to 4 kB. It's a reasonable setting, however for higher throughput applications it would be nice to be able to increase it.

So far I limited to 512 bytes only (same size as SD card sector), that's even worse 😅

I've tried to make the block bigger but somehow didn't work for me. I suspect my hardware setup has some issues and I will solder another breakout board and test it again.

  1. When the application writes a block larger than 4 kB at once, the filesystem implementation still internally splits it into several 4 kB writes. By comparison, FATFS can allocate a sequence of sectors and write them to the card at once.

Yea I think that's another problem. LittleFS probably only does one block at a time. I guess to fix this might need to hack the littlefs_sdmmc_{read|write} functions to a queue or ringbuffer, and perform the actual read/write in a separate thread.

Meanwhile I also found someone mention the SDMMC driver itself is somewhat slow as well: espressif/esp-idf#9240 // EDIT: sorry I misread it. This guy forgot to put the buffer in DMA-able internal SRAM region. Different issues for our case.

@yiming564
Copy link
Author

Hi @huming2207, sorry for taking a week to reply to you.

I have tested LittleFS's writing speed when SD Card is directly connected to my computer via a USB 3.0 SD Card Reader, and the writing speed is about 6 MB/s (5.72 MiB/s):

> dd if=/dev/random of=tf_card/BigFile.txt bs=64K count=1024
1024+0 records in
1024+0 records out
67108864 bytes (67 MB, 64 MiB) copied, 11.1887 s, 6.0 MB/s

P.S. I use littlefs-fuse to access LittleFS on Linux.


And I've also tested FATFS's writing speed when SD Card is connected to ESP32-S3 and writing speed is 13.76 MiB/s, more than twice as much as LittleFS's writing speed:

I (00:00:03.245) app_manager: Start stress test.
I (00:00:03.246) app_manager: Written 0/1024 blk.
I (00:00:03.535) app_manager: Written 64/1024 blk.
I (00:00:03.830) app_manager: Written 128/1024 blk.
I (00:00:04.119) app_manager: Written 192/1024 blk.
I (00:00:04.406) app_manager: Written 256/1024 blk.
I (00:00:04.703) app_manager: Written 320/1024 blk.
I (00:00:04.991) app_manager: Written 384/1024 blk.
I (00:00:05.279) app_manager: Written 448/1024 blk.
I (00:00:05.567) app_manager: Written 512/1024 blk.
I (00:00:05.864) app_manager: Written 576/1024 blk.
I (00:00:06.152) app_manager: Written 640/1024 blk.
I (00:00:06.440) app_manager: Written 704/1024 blk.
I (00:00:06.737) app_manager: Written 768/1024 blk.
I (00:00:07.025) app_manager: Written 832/1024 blk.
I (00:00:07.312) app_manager: Written 896/1024 blk.
I (00:00:07.600) app_manager: Written 960/1024 blk.
I (00:00:07.896) app_manager: Completed!
I (00:00:07.898) app_manager: File size check: 67108864 Bytes

I don't know the specific data structure littlefs used, but ... I wonder maybe littlefs itself is poorly designed?

@BrianPugh
Copy link
Member

BrianPugh commented Jan 17, 2025

we'd really need some sort of profiling to indicate where the performance issue is.

Maybe just as a broad shotgun-style checking, it might be good to see if littlefs is writing a single-block at a time while fatfs is writing multiple:

https://github.com/espressif/esp-idf/blob/0f0068fff3ab159f082133aadfa9baf4fc0c7b8d/components/sdmmc/sdmmc_cmd.c#L513

@BrianPugh
Copy link
Member

to keep conversation focused; closing this one and we can continue #214 .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants