Skip to content

Question about scalability and large files #75

@FreddieChopin

Description

@FreddieChopin

First of all, this is not an "issue", just a question, as I'm not sure whether what I see is normal/expected or not.

I've ported littlefs to my C++ RTOS - https://github.com/DISTORTEC/distortos and I'm trying it out with an SDHC 32 GB card on STM32F429. My test more-or less goes like this:

  • dump flash memory directly to SD card (raw write speed) - 10 times,
  • format the card with littlefs,
  • write the same dump of flash memory to a littlefs file (file system write speed), which always gets truncated when opened - 10 times.

These 3 steps are repeated for write sizes from following sequence: 512 B, 1 kB, 2 kB, ... 512 kB, 1 MB, 2 MB.

After some runs I noticed that difference between speed of raw sequential write to the SD card (with no file system) vs. speed of write with the file system is quite huge, but only for "large" files. The difference grows when the size of file grows - for example when writing just 1 kB, the ratio is just ~2.7, for 128 kB this is already ~7.2, while for 2 MB it's ~14.6 (these are all for average values).

Below is a chart which shows my measurements:

image
(x axis - write size, y axis - ratio between average fs write speed vs average raw write speed)

Here's another interesting observation - in my test I do 10 runs of raw write, format the card with littlefs and do 10 runs of file system write. While the speed of raw writes are mostly identical, the speed of file system writes vary:

  • for small files - below and equal to 16 kB - the first write is actually the slowest one, while next 9 are a bit faster (for example 384 ms first write vs 320 ms second write),
  • for medium-sized files - 32 kB to 128 kB - the speed of file system writes is mostly consistent,
  • for large files - 256 kB to 2 MB - the first write is the fastest, following writes are slower, for 2 MB file the last 3 writes get even slower than earlier 6.

Each write goes to the same file, which I truncate to zero on creation.

Here's the chart of these findings:

image
(x axis - number of write, y axis - ratio write speed of this write vs the speed of first write)

I guess it's also worth noting that I use 512 bytes for read, program and erase block size, and 512 for "lookahead" value.

To summarise:

  1. Is this expected, that for large files the performance is more than 10x lower than raw performance of the SD card, while for small files the difference is much smaller?
  2. Is it expected that different writes (to the same, truncated file) have different speeds?

Maybe this is all related to the value of lookahead? I did not test it yet, as the test takes quite some time (write of 2 MB file takes 60-100 seconds), but maybe this is related? 512 blocks (each 512 B long) of lookahead would correspond roughly to a file size around 256 kB, which is the size where the mentioned effects start to appear...

Thanks in advance for your insight!

--

Below I paste my test code for reference if this may be useful.

int main()
{
  distortos::devices::SpiSdMmcCard card {distortos::board::spi3Master, slaveSelect, 5000000};
  distortos::LittlefsFileSystem fileSystem;

  for (size_t writeSize {512}; writeSize <= 2 * 1024 * 1024; writeSize *= 2)
  {
    {
      const auto ret = card.open();
      if (ret != 0)
      {
        fiprintf(debugStream, "card.open(): %d\r\n", ret);
        distortos::ThisThread::sleepFor(std::chrono::seconds{60});
      }
    }
    for (size_t i {}; i < 10; ++i)
    {
      fiprintf(debugStream, "====================\r\n%zu b sequential write, run %zu\r\n", writeSize, i + 1);
      const auto start = distortos::TickClock::now();
      {
        const auto ret = card.erase(0, writeSize);
        if (ret != 0)
        {
          fiprintf(debugStream, "card.erase(): %d\r\n", ret);
          distortos::ThisThread::sleepFor(std::chrono::seconds{60});
        }
      }
      {
        const auto ret = card.program(0, reinterpret_cast<const void*>(0x8000000), writeSize);
        if (ret.first != 0 || ret.second != writeSize)
        {
          fiprintf(debugStream, "card.program(): %d, %zu\r\n", ret.first, ret.second);
          distortos::ThisThread::sleepFor(std::chrono::seconds{60});
        }
      }
      const auto end = distortos::TickClock::now();
      const auto elapsed = end - start;
      fiprintf(debugStream, "elapsed: %lld ms\r\n", std::chrono::milliseconds{elapsed}.count());
    }
    {
      const auto ret = card.close();
      if (ret != 0)
      {
        fiprintf(debugStream, "card.close(): %d\r\n", ret);
        distortos::ThisThread::sleepFor(std::chrono::seconds{60});
      }
    }
    {
      const auto ret = distortos::LittlefsFileSystem::format(card);
      if (ret != 0)
      {
        fiprintf(debugStream, "distortos::LittlefsFileSystem::format(): %d\r\n", ret);
        distortos::ThisThread::sleepFor(std::chrono::seconds{60});
      }
    }
    {
      const auto ret = fileSystem.mount(card);
      if (ret != 0)
      {
        fiprintf(debugStream, "fileSystem.mount(): %d\r\n", ret);
        distortos::ThisThread::sleepFor(std::chrono::seconds{60});
      }
    }
    for (size_t i {}; i < 10; ++i)
    {
      fiprintf(debugStream, "====================\r\n%zu b file system write, run %zu\r\n", writeSize, i + 1);
      std::unique_ptr<distortos::File> file;
      {
        int ret;
        std::tie(ret, file) = fileSystem.openFile("dump.bin", O_WRONLY | O_CREAT | O_TRUNC);
        if (ret != 0 || file == nullptr)
        {
          fiprintf(debugStream, "fileSystem.openFile(): %d\r\n", ret);
          distortos::ThisThread::sleepFor(std::chrono::seconds{60});
          continue;
        }

      }
      const auto start = distortos::TickClock::now();
      {
        const auto ret = file->write(reinterpret_cast<const void*>(0x8000000), writeSize);
        if (ret.first != 0 || ret.second != writeSize)
        {
          fiprintf(debugStream, "file->write(): %d, %zu\r\n", ret.first, ret.second);
          distortos::ThisThread::sleepFor(std::chrono::seconds{60});
          continue;
        }
      }
      const auto end = distortos::TickClock::now();
      const auto elapsed = end - start;
      fiprintf(debugStream, "elapsed: %lld ms\r\n", std::chrono::milliseconds{elapsed}.count());
    }
    {
      const auto ret = fileSystem.unmount();
      if (ret != 0)
      {
        fiprintf(debugStream, "fileSystem.unmount(): %d\r\n", ret);
        distortos::ThisThread::sleepFor(std::chrono::seconds{60});
      }
    }
  }

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions