测试条件:
- ESP32-S3
- ESP-IDF 版本:v5.1.5
- 闪迪 32G U1 CLASS 10 TF 卡
- FAT32 文件系统,簇大小 4K
测试方法:
每次读取 4K 大小,循环读取,总共读取 16777KB 大小文件,计算平均速率
测试结果:
测试项目 | fread1 | fread(setvbuf NULL) | fread(setvbuf 4K 内部内存) | read | read(buffer 未 4 字节对齐或为 PSRAM 内存)2 |
SDIO 4 线模式(40MHz) | 1594KB/S | 35KB/S | 7636KB/S | 7657KB/S | 1808KB/S |
SDIO 1 线模式(40MHz) | 1286KB/S | 35KB/S | 3470KB/S | 3528KB/S | 1421KB/S |
SPI 模式(20MHz) | 869KB/S | 34KB/S | 1579KB/S | 1592KB/S | 936KB/S |
- fread 性能较差的原因在于,fread 的封装实现增加了一个缓冲区,每次通过 fread 读取数据,实际是从缓冲区拷贝数据;如果缓冲区没有所需数据,则先通过 read 接口读取数据到缓存区。而默认缓冲区大小为 128 字节,所以测试结果相对于 read 接口差距较大(见 提高 I/O 性能) ↩︎
- 当传给 read 的 buffer 为内部内存且 4 字节对齐时,能将读取操作一次性传递到 sdmmc/sdspi 层进行传输;否则传递给 sdmmc/sdspi 层之前会先申请 512 字节大小的内部内存,并将整个读取操作按照最大 512 字节拆分为多次给 sdmmc/sdspi 层操作,造成读取性能下降 ↩︎
SDIO 4 线测试 log
ESP-ROM:esp32s3-20210327
Build:Mar 27 2021
rst:0x1 (POWERON),boot:0xa (SPI_FAST_FLASH_BOOT)
SPIWP:0xee
mode:DIO, clock div:1
load:0x3fce3818,len:0x14e0
load:0x403c9700,len:0x4
load:0x403c9704,len:0x1238
load:0x403cc700,len:0x2edc
entry 0x403c9900
[0;32mI (00:00:00.012) sd_init: using pdrv=0[0m
[0;32mI (00:00:00.014) gpio: GPIO[14]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 [0m
[0;32mI (00:00:00.016) gpio: GPIO[13]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 [0m
[0;32mI (00:00:00.026) gpio: GPIO[21]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 [0m
[0;32mI (00:00:00.036) gpio: GPIO[47]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 [0m
[0;32mI (00:00:00.046) gpio: GPIO[11]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 [0m
[0;32mI (00:00:00.056) gpio: GPIO[12]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0 [0m
[0;32mI (00:00:00.137) sd_init: SD_INSERT[0m
[0;33mW (00:00:00.137) sd_init: Initializing SD card[0m
[0;32mI (00:00:00.190) gpio: GPIO[12]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 [0m
[0;32mI (00:00:00.195) sd_init: SD_MOUNTED[0m
Name: SC32G
Type: SDHC/SDXC
Speed: 40.00 MHz (limit: 40.00 MHz)
Size: 30436MB
CSD: ver=2, sector_size=512, capacity=62333952 read_bl_len=9
SSR: bus_width=4
[0;32mI (00:00:01.057) eds main: file read test(size 16777KB)[0m
[0;33mW (00:00:01.060) eds main: test fread[0m
[0;32mI (00:00:01.060) eds main: fread start[0m
[0;32mI (00:00:11.585) eds main: fread end[0m
[0;33mW (00:00:11.585) eds main: fread speed:1594KB/S
[0m
[0;33mW (00:00:11.586) eds main: test fread[0m
[0;33mW (00:00:11.588) eds main: setvbuf NULL[0m
[0;32mI (00:00:11.593) eds main: fread start[0m
[0;32mI (00:08:05.383) eds main: fread end[0m
[0;33mW (00:08:05.384) eds main: fread speed:35KB/S
[0m
[0;33mW (00:08:05.386) eds main: test fread[0m
[0;33mW (00:08:05.387) eds main: setvbuf 4096[0m
[0;32mI (00:08:05.391) eds main: fread start[0m
[0;32mI (00:08:07.592) eds main: fread end[0m
[0;33mW (00:08:07.592) eds main: fread speed:7636KB/S
[0m
[0;33mW (00:08:07.593) eds main: test read align(0x3fc981e0)[0m
[0;32mI (00:08:07.597) eds main: read start[0m
[0;32mI (00:08:09.792) eds main: read end[0m
[0;33mW (00:08:09.792) eds main: read speed:7657KB/S
[0m
[0;33mW (00:08:09.793) eds main: test read unalign(0x3fc981e1)[0m
[0;32mI (00:08:09.797) eds main: read start[0m
[0;32mI (00:08:19.079) eds main: read end[0m
[0;33mW (00:08:19.080) eds main: read speed:1808KB/S
[0m
[0;32mI (00:08:19.080) main_task: Returned from app_main()[0m
SDIO 1 线测试 log
ESP-ROM:esp32s3-20210327
Build:Mar 27 2021
rst:0x1 (POWERON),boot:0xa (SPI_FAST_FLASH_BOOT)
SPIWP:0xee
mode:DIO, clock div:1
load:0x3fce3818,len:0x14e0
load:0x403c9700,len:0x4
load:0x403c9704,len:0x1238
load:0x403cc700,len:0x2edc
entry 0x403c9900
[0;32mI (00:00:00.012) sd_init: using pdrv=0[0m
[0;32mI (00:00:00.014) gpio: GPIO[14]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 [0m
[0;32mI (00:00:00.016) gpio: GPIO[13]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 [0m
[0;32mI (00:00:00.026) gpio: GPIO[21]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 [0m
[0;32mI (00:00:00.036) gpio: GPIO[47]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 [0m
[0;32mI (00:00:00.046) gpio: GPIO[11]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 [0m
[0;32mI (00:00:00.056) gpio: GPIO[12]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0 [0m
[0;32mI (00:00:00.137) sd_init: SD_INSERT[0m
[0;33mW (00:00:00.137) sd_init: Initializing SD card[0m
[0;32mI (00:00:00.195) sd_init: SD_MOUNTED[0m
Name: SC32G
Type: SDHC/SDXC
Speed: 40.00 MHz (limit: 40.00 MHz)
Size: 30436MB
CSD: ver=2, sector_size=512, capacity=62333952 read_bl_len=9
SSR: bus_width=1
[0;32mI (00:00:01.057) eds main: file read test(size 16777KB)[0m
[0;33mW (00:00:01.060) eds main: test fread[0m
[0;32mI (00:00:01.060) eds main: fread start[0m
[0;32mI (00:00:14.107) eds main: fread end[0m
[0;33mW (00:00:14.107) eds main: fread speed:1286KB/S
[0m
[0;33mW (00:00:14.108) eds main: test fread[0m
[0;33mW (00:00:14.110) eds main: setvbuf NULL[0m
[0;32mI (00:00:14.114) eds main: fread start[0m
[0;32mI (00:08:10.427) eds main: fread end[0m
[0;33mW (00:08:10.428) eds main: fread speed:35KB/S
[0m
[0;33mW (00:08:10.431) eds main: test fread[0m
[0;33mW (00:08:10.431) eds main: setvbuf 4096[0m
[0;32mI (00:08:10.435) eds main: fread start[0m
[0;32mI (00:08:15.273) eds main: fread end[0m
[0;33mW (00:08:15.273) eds main: fread speed:3470KB/S
[0m
[0;33mW (00:08:15.274) eds main: test read align(0x3fc981e0)[0m
[0;32mI (00:08:15.278) eds main: read start[0m
[0;32mI (00:08:20.037) eds main: read end[0m
[0;33mW (00:08:20.037) eds main: read speed:3528KB/S
[0m
[0;33mW (00:08:20.038) eds main: test read unalign(0x3fc981e1)[0m
[0;32mI (00:08:20.041) eds main: read start[0m
[0;32mI (00:08:31.846) eds main: read end[0m
[0;33mW (00:08:31.847) eds main: read speed:1421KB/S
[0m
[0;32mI (00:08:31.847) main_task: Returned from app_main()[0m
SPI 测试 log
ESP-ROM:esp32s3-20210327
Build:Mar 27 2021
rst:0x1 (POWERON),boot:0xb (SPI_FAST_FLASH_BOOT)
SPIWP:0xee
mode:DIO, clock div:1
load:0x3fce3818,len:0x14e0
load:0x403c9700,len:0x4
load:0x403c9704,len:0x1238
load:0x403cc700,len:0x2edc
entry 0x403c9900
I (00:00:00.012) eds main: Initializing SD card
I (00:00:00.013) eds main: Using SPI peripheral
I (00:00:00.013) eds main: Mounting filesystem
I (00:00:00.017) gpio: GPIO[12]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
I (00:00:00.028) sdspi_transaction: cmd=52, R1 response: command not supported
I (00:00:00.068) sdspi_transaction: cmd=5, R1 response: command not supported
I (00:00:00.113) eds main: Filesystem mounted
Name: SC32G
Type: SDHC/SDXC
Speed: 20.00 MHz (limit: 20.00 MHz)
Size: 30436MB
CSD: ver=2, sector_size=512, capacity=62333952 read_bl_len=9
SSR: bus_width=1
I (00:00:00.122) eds main: file read test(size 16777KB)
W (00:00:00.131) eds main: test fread
I (00:00:00.132) eds main: fread start
I (00:00:19.426) eds main: fread end
W (00:00:19.426) eds main: fread speed:869KB/S
W (00:00:19.427) eds main: test fread
W (00:00:19.429) eds main: setvbuf NULL
I (00:00:19.433) eds main: fread start
I (00:08:21.862) eds main: fread end
W (00:08:21.862) eds main: fread speed:34KB/S
W (00:08:21.866) eds main: test fread
W (00:08:21.866) eds main: setvbuf 4096
I (00:08:21.869) eds main: fread start
I (00:08:32.497) eds main: fread end
W (00:08:32.497) eds main: fread speed:1579KB/S
W (00:08:32.498) eds main: test read align(0x3fc99cd0)
I (00:08:32.501) eds main: read start
I (00:08:43.038) eds main: read end
W (00:08:43.039) eds main: read speed:1592KB/S
W (00:08:43.040) eds main: test read unalign(0x3fc99cd1)
I (00:08:43.043) eds main: read start
I (00:09:00.955) eds main: read end
W (00:09:00.956) eds main: read speed:936KB/S
I (00:09:00.956) main_task: Returned from app_main()
非对齐读取测试:
打开文件后先读取一小块数据,使后续每次读取起始位置均未对齐文件系统的簇大小 4K。此处仅测试前面读取性能最佳的两种情况下的性能差异,SDIO 4线模式 40MHz
测试结果:
测试项目 | 0 | 123 | 512 (TF 卡 block 大小对齐) | 1025 |
fread(setvbuf 4K 内部内存) | 7643KB/S | 7615KB/S | 7639KB/S | 7612KB/S |
read | 7653KB/S | 1789KB/S | 5125KB/S | 1784KB/S |
测试 log
ESP-ROM:esp32s3-20210327
Build:Mar 27 2021
rst:0x1 (POWERON),boot:0xa (SPI_FAST_FLASH_BOOT)
SPIWP:0xee
mode:DIO, clock div:1
load:0x3fce3818,len:0x14e0
load:0x403c9700,len:0x4
load:0x403c9704,len:0x1238
load:0x403cc700,len:0x2edc
entry 0x403c9900
I (00:00:00.012) sd_init: using pdrv=0
I (00:00:00.014) gpio: GPIO[14]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (00:00:00.016) gpio: GPIO[13]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (00:00:00.026) gpio: GPIO[21]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (00:00:00.036) gpio: GPIO[47]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (00:00:00.046) gpio: GPIO[11]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (00:00:00.056) gpio: GPIO[12]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
I (00:00:00.137) sd_init: SD_INSERT
W (00:00:00.137) sd_init: Initializing SD card
I (00:00:00.190) gpio: GPIO[12]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (00:00:00.195) sd_init: SD_MOUNTED
I (00:00:01.057) eds main: file read test(size 16777KB)
W (00:00:01.060) eds main: test fread
W (00:00:01.060) eds main: setvbuf 4096
I (00:00:01.061) eds main: fread start
I (00:00:03.260) eds main: fread end
W (00:00:03.260) eds main: fread speed:7643KB/S
W (00:00:03.261) eds main: test fread
W (00:00:03.263) eds main: setvbuf 4096
W (00:00:03.268) eds main: offset 123
I (00:00:03.275) eds main: fread start
I (00:00:05.480) eds main: fread end
W (00:00:05.480) eds main: fread speed:7615KB/S
W (00:00:05.481) eds main: test fread
W (00:00:05.483) eds main: setvbuf 4096
W (00:00:05.487) eds main: offset 512
I (00:00:05.494) eds main: fread start
I (00:00:07.692) eds main: fread end
W (00:00:07.692) eds main: fread speed:7639KB/S
W (00:00:07.693) eds main: test fread
W (00:00:07.695) eds main: setvbuf 4096
W (00:00:07.700) eds main: offset 1025
I (00:00:07.707) eds main: fread start
I (00:00:09.912) eds main: fread end
W (00:00:09.913) eds main: fread speed:7612KB/S
W (00:00:09.914) eds main: test read align(0x3fc981e0)
I (00:00:09.917) eds main: read start
I (00:00:12.113) eds main: read end
W (00:00:12.113) eds main: read speed:7653KB/S
W (00:00:12.114) eds main: test read align(0x3fc981e0)
W (00:00:12.117) eds main: offset 123
I (00:00:12.122) eds main: read start
I (00:00:21.499) eds main: read end
W (00:00:21.500) eds main: read speed:1789KB/S
W (00:00:21.500) eds main: test read align(0x3fc981e0)
W (00:00:21.504) eds main: offset 512
I (00:00:21.511) eds main: read start
I (00:00:24.785) eds main: read end
W (00:00:24.785) eds main: read speed:5125KB/S
W (00:00:24.786) eds main: test read align(0x3fc981e0)
W (00:00:24.789) eds main: offset 1025
I (00:00:24.795) eds main: read start
I (00:00:34.199) eds main: read end
W (00:00:34.199) eds main: read speed:1784KB/S
I (00:00:34.199) main_task: Returned from app_main()
补充:上述非对齐测试,是通过 fread/read 读取一小节数据来调整测试起始读取位置非对齐;如果 fread 测试,改用 fseek 直接调整读取起始位置到非对齐位置,则读取性能也会下降(offset 为 123、512、1025 的测试情况分别为 1772KB/S、4999KB/S、1766KB/S,与 read 情况接近)。因此建议需要修改读取位置的时候,先使用 fseek 调整到 fread 缓冲区大小对齐位置,再使用 fread 补齐剩余非对齐部分偏移。
总结:
在内存充足或有 PSRAM 的情况下,如果应用层读取无法保证簇对齐、buffer 空间为内部内存、buffer 地址 4 字节对齐等这几种情况,建议使用 fread 接口,并设置 4K 或者簇大小的内部内存缓冲 buffer;其他情况则使用 read 接口
测试代码
#define TEST_LEN 4*1024
#define TEST_COUNT 4*1024
uint8_t read_buff[TEST_LEN + 1];
uint8_t f_buff[0x1000];
void test_fread(bool set_buff, size_t buff_len, uint32_t offset)
{
uint32_t time_start, time_end;
int speed;
FILE *f;
f = fopen("/sdcard/test.bin", "r");
if (f != NULL) {
ESP_LOGW(TAG, "test fread");
if (set_buff) {
if (buff_len) {
ESP_LOGW(TAG, "setvbuf %d", buff_len);
setvbuf(f, (char *)f_buff, _IOFBF, buff_len);
} else {
ESP_LOGW(TAG, "setvbuf NULL");
setvbuf(f, NULL, _IONBF, 0);
}
}
if(offset){
if(offset > TEST_LEN){
offset = TEST_LEN;
}
ESP_LOGW(TAG, "offset %"PRIu32, offset);
fread(read_buff, offset, 1, f);
}
ESP_LOGI(TAG, "fread start");
time_start = esp_timer_get_time() / 1000;
for (int i = 0;i < TEST_COUNT;i++) {
fread(read_buff, TEST_LEN, 1, f);
}
time_end = esp_timer_get_time() / 1000;
ESP_LOGI(TAG, "fread end");
fclose(f);
speed = TEST_LEN * TEST_COUNT / (time_end - time_start);
ESP_LOGW(TAG, "fread speed:%dKB/S\n", speed);
}
}
void test_read(bool align, uint32_t offset)
{
uint32_t time_start, time_end;
int speed;
int fd;
fd = open("/sdcard/test.bin", O_RDONLY);
uint8_t *buff = read_buff;
if (fd >= 0) {
if (align) {
ESP_LOGW(TAG, "test read align(%p)", buff);
} else {
buff += 1;
ESP_LOGW(TAG, "test read unalign(%p)", buff);
}
if(offset){
if(offset > TEST_LEN){
offset = TEST_LEN;
}
ESP_LOGW(TAG, "offset %"PRIu32, offset);
read(fd, buff, offset);
}
ESP_LOGI(TAG, "read start");
time_start = esp_timer_get_time() / 1000;
for (int i = 0;i < TEST_COUNT;i++) {
read(fd, buff, TEST_LEN);
}
time_end = esp_timer_get_time() / 1000;
ESP_LOGI(TAG, "read end");
close(fd);
speed = TEST_LEN * TEST_COUNT / (time_end - time_start);
ESP_LOGW(TAG, "read speed:%dKB/S\n", speed);
}
}
void file_test()
{
ESP_LOGI(TAG, "file read test(size %dKB)", TEST_LEN * TEST_COUNT / 1000);
#if 1
test_fread(false, 0, 0);
test_fread(true, 0, 0);
test_fread(true, 4096, 0);
test_read(true, 0);
test_read(false, 0);
#else
test_fread(true, 4096, 0);
test_fread(true, 4096, 123);
test_fread(true, 4096, 512);
test_fread(true, 4096, 1025);
test_read(true, 0);
test_read(true, 123);
test_read(true, 512);
test_read(true, 1025);
#endif
}