ESP32 - LITTLEFS 10 times slower than SPIFFS

Hi Everybody,

I have a project in mind where I have to write sensor data into file in ESP32 flash memory (yes, I am aware of 10000 writes limit). As I understood SPIFFS is deprecated and LittleFS would be the way to go.

I decided to do a little speed test first. I uploaded the Arduino IDE ESP32 LITTLEFS_test.ino sketch onto the regular ESP32 Dev Module and added a couple of lines of code into loop to measure the append time of text "qwerty" to the file data.txt on ESP32 file system. I uploaded the file via ESP32 Sketch Data Upload tool with LittleFS, that seems to format the flash for using LittleFS.

To my great surprise the append function run time was 20-30ms in the beginning and when the data.txt file grew to a few hundred bytes it became 50-70ms. And there is very odd behavior - every 15th time the append operation takes 250-350ms. That does not depend on the append function call period. Extra long append time occurs every 15th time would the call period be 1, 2 or 3 seconds.

When I run the same script modified to use SPIFFS the append function run time is steady 5ms.

Would someone more knowledgeable on the subject explain me please what I am doing wrong. Or is the long append time problem with LITTLEFS and I should proceed with SPIFFS? Thank you in advance.

I add hereby also the shortened version of the LITTLEFS_test.ino sketch I used.

Compile environment:
Arduino IDE 1.8.19 on Win10 64bit
ESP32 systems version 2.0.2

The ESP32 board info:
Board: "ESP32 Dev Module"
Upload Speed: "921600"
CPU Frequency: "240MHz (WiFi/BT"
Flash Frequency: "80MHz"
Flash Mode: "QIO"
Flash Size: "4MB (32Mb)"
Partition Scheme: "No OTA (2MB APP 2MB SPIFFS)"
Core Debug Level: "None"
PSRAM: "Disabled"
Arduino Runs On: Core1
Events Run On: Core1
Port: "COM9"

#include "FS.h"
#include "LittleFS.h"

#define FORMAT_LITTLEFS_IF_FAILED true

unsigned long lastAppendMillis = 0; // append function call period, ms
unsigned long beginAppendMillis = 0; // append function start time, ms
unsigned long appendMillis = 0; // append function end time, ms

void setup(){
    Serial.begin(115200);

    if(!LittleFS.begin(FORMAT_LITTLEFS_IF_FAILED)){
        Serial.println("LittleFS Mount Failed");
        return;
    }

    Serial.println("File system info:"); 
    Serial.print("  Total space:      ");
    Serial.print(LittleFS.totalBytes());
    Serial.println("byte");
 
    Serial.print("  Total space used: ");
    Serial.print(LittleFS.usedBytes());
    Serial.println("byte"); 
    Serial.println();
 
    listDir(LittleFS, "/", 3);
}

void loop(){

	if (millis() - lastAppendMillis > 1000) { 
		lastAppendMillis = millis();

		Serial.println("###########################");
		Serial.println("Append qwerty");

		beginAppendMillis = millis(); 

		appendFile(LittleFS, "/data.txt", "qwerty\r\n");	

		appendMillis = millis() - beginAppendMillis;
		Serial.print("appendMillis = ");
		Serial.print(appendMillis);	
		Serial.println(" ms");
	}
}

// LittleFS functions
void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
    Serial.printf("Listing directory: %s\r\n", dirname);

    File root = fs.open(dirname);
    if(!root){
        Serial.println("- failed to open directory");
        return;
    }
    if(!root.isDirectory()){
        Serial.println(" - not a directory");
        return;
    }

    File file = root.openNextFile();
    while(file){
        if(file.isDirectory()){
            Serial.print("  DIR : ");
            Serial.println(file.name());
            if(levels){
                listDir(fs, file.path(), levels -1);
            }
        } else {
            Serial.print("  FILE: ");
            Serial.print(file.name());
            Serial.print("\tSIZE: ");
            Serial.println(file.size());
        }
        file = root.openNextFile();
    }
}

void appendFile(fs::FS &fs, const char * path, const char * message){ 
    Serial.printf("Appending to file: %s\r\n", path);
    File file = fs.open(path, FILE_APPEND);
    if(!file){
        Serial.println("- failed to open file for appending");
        return;
    }
    if(file.print(message)){
        Serial.println("- message appended");
    } else {
        Serial.println("- append failed");
    }
    file.close();
}

I also think this is important. Appending a log file is a very common use case. I understand this was a design decision to make LittleFS crash proof but still there must be a better way.

The way I would recommend is to use FRAM. 32K x 8 for under $6.00 from my favorite china supplier. It operates at memory at speed, no delays for write or read. It is non volatile which says it remembers without power. Read/Write cycles in excess of a billion cycles. It will work with the ESP devices with either SPI or I2C without delays.

HI.

I am using littlefs on an SD card with an STM32 MCU and I have a very similar issue.
I have reported it on littlefs GitHub page under:
https://github.com/littlefs-project/littlefs/issues/701

Maybe you can add some more of your details to get at least an explanation why this is happening and if there is a way to avoid it.

Well I think I actually know why appends take longer and longer. It’s because LittleFS wants to be crash proof so every append first makes a copy, appends to the copy, then replaces the original. In this way a crash during append can not corrupt the original file. The LittleFS folks know all about this, it’s by design, I just wish they could come up with a different algorithm because appending is so common for IoT and deserves special attention.

Thank you for your reply.

I would understand it makes a copy, but why isn't it stuck on every append then?
It gets stuck just every N-th append periodically?

I would expect it to get stuck on every append but that does not happen looking at the trace. It must have to do something with block size, file size or block count.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.