エンジニアとしての市場価値を測りませんか?PR

企業からあなたに合ったオリジナルのスカウトを受け取って、市場価値を測りましょう

0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ESP32-S3 SDカード書き込み速度比較

Last updated at Posted at 2024-11-24

はじめに

ESP32 WROOM 32EでSdFat.hを使用して1kHzのデータロギングを試みた際に性能不足の問題に直面しました。
そこで、次世代機としてESP32 S3への移行を検討するにあたり、以下の3つのSDカードライブラリの性能を比較評価しました。

  • SD.h - Arduino標準ライブラリ
  • SdFat.h - 高性能を謳うサードパーティライブラリ
  • SD_MMC.h - ESP32のMMCモード専用ライブラリ

本記事は概略的な性能比較を目的としており、厳密な測定ではありません。

環境

  • macOS Sonoma
  • PlatformIO
  • ESP32-S3- 開発ボード
  • マイクロSDカードスロットDIP化キット
  • KIOXIAマイクロSDカード16GB

評価方法

次の3つを評価しました。

  1. 書き込み速度(MB/s)
    • 一定サイズのデータを書き込む時間を計測
  2. レイテンシ(ms)
    • 書き込み開始から完了までの遅延時間
    • 最大/平均/最小を計測
  3. IOPS (Input/Output Operations Per Second)
    • 1秒あたりの書き込み操作回数

(本当はSDカードも数種類準備した方が良いですが、お金がないため、家にあった1種類のみで評価しました。)

ファイルサイズは、64KB/256KB/1MBの3種類で、それぞれのファイルサイズ、ライブラリで10回ずつ測定し、平均を求めました。

コード

SD_MMC.h
main.cpp
#include <Arduino.h>
#include <SD_MMC.h>

class SDMMCBenchmark {
private:
  const size_t BUFFER_SIZE = 512;
  uint8_t *buffer;

public:
  SDMMCBenchmark() {
    buffer = new uint8_t[BUFFER_SIZE];
    for (size_t i = 0; i < BUFFER_SIZE; i++) {
      buffer[i] = random(0, 255);
    }
  }

  ~SDMMCBenchmark() { delete[] buffer; }

  void printCSVHeader() {
    Serial.println(
        "Iteration,FileSize(KB),BlockSize(B),WriteSpeed(MB/s),Latency(ms),MinLatency(ms),MaxLatency(ms),IOPS");
  }

  void runBenchmark(const char *filename, size_t totalBytes, int iterations = 10) {
    for (int i = 0; i < iterations; i++) {
      File file = SD_MMC.open(filename, FILE_WRITE);
      if (!file) {
        Serial.println("Error: Failed to open file for writing");
        return;
      }

      unsigned long startTime = millis();
      size_t bytesWritten = 0;
      int operations = 0;

      float minLatency = 999999;
      float maxLatency = 0;
      float totalLatency = 0;

      while (bytesWritten < totalBytes) {
        unsigned long writeStart = micros();
        size_t written = file.write(buffer, BUFFER_SIZE);
        unsigned long writeEnd = micros();

        float latency = (writeEnd - writeStart) / 1000.0;
        minLatency = min(minLatency, latency);
        maxLatency = max(maxLatency, latency);
        totalLatency += latency;

        bytesWritten += written;
        operations++;
      }

      unsigned long endTime = millis();
      float duration = (endTime - startTime) / 1000.0;

      float speed = (bytesWritten / 1024.0 / 1024.0) / duration;
      float avgLatency = totalLatency / operations;
      float iops = operations / duration;

      Serial.print(i + 1);
      Serial.print(",");
      Serial.print(totalBytes / 1024.0, 2);
      Serial.print(",");
      Serial.print(BUFFER_SIZE);
      Serial.print(",");
      Serial.print(speed, 2);
      Serial.print(",");
      Serial.print(avgLatency, 2);
      Serial.print(",");
      Serial.print(minLatency, 2);
      Serial.print(",");
      Serial.print(maxLatency, 2);
      Serial.print(",");
      Serial.println(iops, 0);

      file.close();
      delay(3000);
    }
  }
};

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

  if (!SD_MMC.setPins(14, 15, 2, 4, 12, 13)) { // これらのピン番号は例です
    Serial.println("Pin configuration failed!");
    return;
  }

  if (!SD_MMC.begin()) {
    Serial.println("SD_MMC initialization failed!");
    return;
  }

  SDMMCBenchmark benchmark;

  Serial.println("SD_MMC initialization succeeded!");
  benchmark.printCSVHeader();

  const size_t sizes[] = {64 * 1024, 256 * 1024, 1024 * 1024}; // 64KB, 256KB, 1MB

  for (size_t size : sizes) {
    String filename = "/test_" + String(size / 1024) + "kb.txt";
    benchmark.runBenchmark(filename.c_str(), size);
    delay(5000);
  }
}

void loop() {
  // Empty
}
SD.h
main.cpp
#include <Arduino.h>
#include <SD.h>

#define CUSTOM_CS 4
#define CUSTOM_MOSI 5
#define CUSTOM_MISO 6
#define CUSTOM_SCK 7

class SDBenchmark {
private:
  const size_t BUFFER_SIZE = 512;
  uint8_t *buffer;
  const int chipSelect = CUSTOM_CS;

public:
  SDBenchmark() {
    buffer = new uint8_t[BUFFER_SIZE];
    for (size_t i = 0; i < BUFFER_SIZE; i++) {
      buffer[i] = random(0, 255);
    }
  }

  ~SDBenchmark() { delete[] buffer; }

  void printCSVHeader() {
    Serial.println(
        "Iteration,FileSize(KB),BlockSize(B),WriteSpeed(MB/s),Latency(ms),MinLatency(ms),MaxLatency(ms),IOPS");
  }

  void runBenchmark(const char *filename, size_t totalBytes, int iterations = 10) {
    for (int i = 0; i < iterations; i++) {
      File file = SD.open(filename, FILE_WRITE);
      if (!file) {
        Serial.println("Error: Failed to open file for writing");
        return;
      }

      unsigned long startTime = millis();
      size_t bytesWritten = 0;
      int operations = 0;

      float minLatency = 999999;
      float maxLatency = 0;
      float totalLatency = 0;

      while (bytesWritten < totalBytes) {
        unsigned long writeStart = micros();
        size_t written = file.write(buffer, BUFFER_SIZE);
        unsigned long writeEnd = micros();

        float latency = (writeEnd - writeStart) / 1000.0;
        minLatency = min(minLatency, latency);
        maxLatency = max(maxLatency, latency);
        totalLatency += latency;

        bytesWritten += written;
        operations++;
      }

      unsigned long endTime = millis();
      float duration = (endTime - startTime) / 1000.0;

      float speed = (bytesWritten / 1024.0 / 1024.0) / duration;
      float avgLatency = totalLatency / operations;
      float iops = operations / duration;

      Serial.print(i + 1);
      Serial.print(",");
      Serial.print(totalBytes / 1024.0, 2);
      Serial.print(",");
      Serial.print(BUFFER_SIZE);
      Serial.print(",");
      Serial.print(speed, 2);
      Serial.print(",");
      Serial.print(avgLatency, 2);
      Serial.print(",");
      Serial.print(minLatency, 2);
      Serial.print(",");
      Serial.print(maxLatency, 2);
      Serial.print(",");
      Serial.println(iops, 0);

      file.close();
      delay(3000);
    }
  }
};

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

  SPI.begin(CUSTOM_SCK, CUSTOM_MISO, CUSTOM_MOSI, CUSTOM_CS);
  SPI.setDataMode(SPI_MODE0);

  if (!SD.begin(CUSTOM_CS)) {
    Serial.println("SD initialization failed!");
    return;
  }

  SDBenchmark benchmark;

  Serial.println("SD initialization succeeded!");
  benchmark.printCSVHeader();

  const size_t sizes[] = {64 * 1024, 256 * 1024, 1024 * 1024}; // 64KB, 256KB, 1MB

  for (size_t size : sizes) {
    String filename = "/test_" + String(size / 1024) + "kb.txt";
    benchmark.runBenchmark(filename.c_str(), size);
    delay(5000);
  }
}

void loop() {
  // Empty
}
SdFat.h
main.cpp
#include <Arduino.h>
#include <SPI.h>
#include <SdFat.h>

#define CUSTOM_CS 4
#define CUSTOM_MOSI 5
#define CUSTOM_MISO 6
#define CUSTOM_SCK 7

SdFat SDFAT;

class SDBenchmark {
private:
  const size_t BUFFER_SIZE = 512;
  uint8_t *buffer;
  const int chipSelect = CUSTOM_CS;

public:
  SDBenchmark() {
    buffer = new uint8_t[BUFFER_SIZE];
    for (size_t i = 0; i < BUFFER_SIZE; i++) {
      buffer[i] = random(0, 255);
    }
  }

  ~SDBenchmark() { delete[] buffer; }

  void printCSVHeader() {
    Serial.println(
        "Iteration,FileSize(KB),BlockSize(B),WriteSpeed(MB/s),Latency(ms),MinLatency(ms),MaxLatency(ms),IOPS");
  }

  void runBenchmark(const char *filename, size_t totalBytes, int iterations = 10) {
    for (int i = 0; i < iterations; i++) {
      File32 file;
      file.open(filename, O_RDWR | O_CREAT | O_AT_END);
      if (!file) {
        Serial.println("Error: Failed to open file for writing");
        return;
      }

      unsigned long startTime = millis();
      size_t bytesWritten = 0;
      int operations = 0;

      float minLatency = 999999;
      float maxLatency = 0;
      float totalLatency = 0;

      while (bytesWritten < totalBytes) {
        unsigned long writeStart = micros();
        size_t written = file.write(buffer, BUFFER_SIZE);
        unsigned long writeEnd = micros();

        float latency = (writeEnd - writeStart) / 1000.0;
        minLatency = min(minLatency, latency);
        maxLatency = max(maxLatency, latency);
        totalLatency += latency;

        bytesWritten += written;
        operations++;
      }

      unsigned long endTime = millis();
      float duration = (endTime - startTime) / 1000.0;

      float speed = (bytesWritten / 1024.0 / 1024.0) / duration;
      float avgLatency = totalLatency / operations;
      float iops = operations / duration;

      Serial.print(i + 1);
      Serial.print(",");
      Serial.print(totalBytes / 1024.0, 2);
      Serial.print(",");
      Serial.print(BUFFER_SIZE);
      Serial.print(",");
      Serial.print(speed, 2);
      Serial.print(",");
      Serial.print(avgLatency, 2);
      Serial.print(",");
      Serial.print(minLatency, 2);
      Serial.print(",");
      Serial.print(maxLatency, 2);
      Serial.print(",");
      Serial.println(iops, 0);

      file.close();
      delay(3000);
    }
  }
};

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

  SPI.begin(CUSTOM_SCK, CUSTOM_MISO, CUSTOM_MOSI, CUSTOM_CS);
  SPI.setDataMode(SPI_MODE0);

  if (!SDFAT.begin(CUSTOM_CS, SD_SCK_MHZ(10))) {
    Serial.println("SD initialization failed!");
    return;
  }

  SDBenchmark benchmark;

  Serial.println("SD initialization succeeded!");
  benchmark.printCSVHeader();

  const size_t sizes[] = {64 * 1024, 256 * 1024, 1024 * 1024}; // 64KB, 256KB, 1MB

  for (size_t size : sizes) {
    String filename = "/test_" + String(size / 1024) + "kb.txt";
    benchmark.runBenchmark(filename.c_str(), size);
    delay(5000);
  }
}

void loop() {
  // Empty
}

ピン接続

SD.h&SdFat.h
ESP32 pin SPI pin name SD card pin Notes
4 CS CS -
5 MOSI MOSI/DI 10k pullup
6 MISO MISO/DO 10k pullup
7 SCK SCK/CLK 10k pullup
SD_MMC.h
ESP32 pin SD card pin Notes
IO14 CLK 10k pullup
IO15 CMD 10k pullup
IO2 D0 10k pullup
IO4 D1 10k pullup
IO12 D2 10k pullup
IO13 D3 10k pullup

どちらともVSSとVDDの間にそこそこ容量の大きいパスコンを配置してください。

結果

長いので平均のみ載せます。

取得した値の平均
image.png

グラフ
image.png

image.png

image.png

見ずらいのでSDMMCを除いたグラフも作成しました。
image.png

image.png

image.png

測定結果から

書き込み速度について

  • 全てのファイルサイズで約3.9MB/sを維持
  • SD.h、SdFat.hと比較して約8-9倍の速度差
  • ファイルサイズによる性能変動が少ない
  • SD.hとSdFat.hはほぼ同じだが、SdFat.hがやや速い
    -> SD.h、SdFat.hがデータ線2本のSPIに対してSD_MMC.hは4本だから?

レイテンシについて

  • SD_MMC.hは最小レイテンシで0.05ms以下を実現
  • SD.hとSdFat.hは似た傾向を示すが、SdFat.hがやや短い

IOPSについて

  • SD_MMC.hが最も高い
  • ファイルサイズが大きくなるにつれてIOPSは低下傾向

終わりに

思ったよりSD_MMCが圧倒的な速さでした。

Qiita投稿2回目&メモしながら書いた記事なので、間違い等ありましたらコメントで教えていただけるとありがたいです。

参考にしたサイト

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?