Arduino Mega 2560 SPI/SdFat Performance

From Wiki
Jump to navigationJump to search

These are some tests using an Arduino Mega 2560 @ 16Mhz with the SdFat library and software vs hardware SPI. There are a total of four tests:

  1. Write a single file of 4K
  2. Read a single file of 4K
  3. Write 4 files of 4K, interleaving each byte read
  4. Read 4 files of 4K, interleaving each byte written

Note: The hardware SPI test numbers are not yet available

Sandisk 1GB (mfg 06/2008)
SPI Type Test Total Bytes Time
Software Single Write 4096 221ms
Software Single Read 4096 174ms
Software Multi Write 16384 224862ms
Software Multi Read 16384 72853ms


Kingston 2GB1GB (mfg 09/2010)
SPI Type Test Total Bytes Time
Software Single Write 4096 549ms
Software Single Read 4096 180ms
Software Multi Write 16384 244672ms
Software Multi Read 16384 76029ms

Reading and writing a byte at a time was a deliberate decision to test (what should be) the worst case performance of the SdFat library. An application I have can have up to 4 files open at any one time for writing, plus 2 for reading. I wanted to test what the performance hit would be with SdFat constantly having to re-read sectors from the SD card since it has only a single buffer to work with.

This is the code for the various tests. There is nothing particularly special about any of it. The storageGetRoot() function returns a 'SdFile *' pointer to an 'SdFile' object that's a static named 'root' in another module, and was initialized with 'root.openRoot (&volume)'. monitorPrintf_P() is a function that uses snprintf_P to format a print string and send it to a specific serial port. DOS83_FILENAME_SIZE is a #define for the number 13 (8 characters of the base name, a period, 3 characters of extension, and a null terminator).

Code: Single 4K file write test
 
static void monitorCmdTestWriteSingle (void)
{
  SdFile w;
  char fileName [DOS83_FILENAME_SIZE];
  unsigned long timeWriteStart;
  unsigned long timeDone;
  unsigned long bytesWritten;
  char c = 'A';

  monitorCmdTestCleanup ();

  strcpy_P (fileName, PSTR ("TESTFILE.001"));

    if (!w.open (storageGetRoot (), fileName, O_CREAT | O_TRUNC | O_RDWR))
    {
      monitorPrintf_P (PSTR ("Cannot create file \"%s\"\r\n"), fileName);
      return;
    }

  for (timeWriteStart = millis (), bytesWritten = 0; bytesWritten < 4096; bytesWritten++)
  {
    if (w.write (&c, sizeof (c)) != sizeof (c))
    {
      monitorPrintf_P (PSTR ("Cannot write file\r\n"));
      return;
    }
  }

  w.close ();

  timeDone = millis ();

  monitorPrintf_P (PSTR ("write time  : %lums\r\n"), timeDone - timeWriteStart);
  monitorPrintf_P (PSTR ("total bytes : %lu\r\n"), bytesWritten);
}
  


Code: Single 4K file readtest
 
static void monitorCmdTestReadSingle (void)
{
  SdFile w;
  unsigned long timeReadStart;
  unsigned long timeDone;
  unsigned long bytesRead;
  char fileName [DOS83_FILENAME_SIZE];

  strcpy_P (fileName, PSTR ("TESTFILE.001"));

  if (!w.open (storageGetRoot (), fileName, O_READ))
  {
    monitorPrintf_P (PSTR ("Cannot open file \"%s\"\r\n"), fileName);
    return;
  }

  for (timeReadStart = millis (), bytesRead = 0; true; )
  {
    char c;
    int l;

    if ((l = w.read (&c, sizeof (c))) < 0)
    {
      monitorPrintf_P (PSTR ("Cannot read file\r\n"));
      return;
    }
    else if (!l)
    {
      w.close ();
      break;
    }
    else
      bytesRead += l;
  }

  timeDone = millis ();

  monitorPrintf_P (PSTR ("read time   : %lums\r\n"), timeDone - timeReadStart);
  monitorPrintf_P (PSTR ("total bytes : %lu\r\n"), bytesRead);
}
  


Code: Multi 4K file write test
 
static int monitorCmdTestWriteMulti (void)
{
  SdFile w [4];
  char fileName [DOS83_FILENAME_SIZE];
  unsigned long timeWriteStart;
  unsigned long timeDone;
  unsigned long bytesWritten;
  int i;

  strcpy_P (fileName, PSTR ("TESTFILE.001"));

  for (i = 0; i < 4; i++, fileName [sizeof (fileName) - 2]++)
  {
    if (!w [i].open (storageGetRoot (), fileName, O_CREAT | O_TRUNC | O_RDWR))
    {
      monitorPrintf_P (PSTR ("Cannot create file \"%s\"\r\n"), fileName);
      return -1;
    }
  }

  for (timeWriteStart = millis (), bytesWritten = 0, i = 0; bytesWritten < (4 * 4096); i = (i + 1) & 3)
  {
    char c = 'A' + i;

    if (w [i].write (&c, sizeof (c)) != sizeof (c))
    { 
      monitorPrintf_P (PSTR ("Cannot read write %d\r\n"), i);
      return 0;
    }

    bytesWritten++;
  }

  for (i = 0; i < 4; i++)
    w [i].close ();

  timeDone = millis ();

  monitorPrintf_P (PSTR ("write time  : %lums\r\n"), timeDone - timeWriteStart);
  monitorPrintf_P (PSTR ("total bytes : %lu\r\n"), bytesWritten);
}
  


Code: Multi 4K file read test
 
static void monitorCmdTestReadMulti (void)
{
  SdFile w [4];
  char fileName [DOS83_FILENAME_SIZE];
  unsigned long timeReadStart;
  unsigned long timeDone;
  unsigned long bytesRead;
  int i;

  strcpy_P (fileName, PSTR ("TESTFILE.001"));

  for (i = 0; i < 4; i++, fileName [sizeof (fileName) - 2]++)
  {
    if (!w [i].open (storageGetRoot (), fileName, O_READ))
    { 
      monitorPrintf_P (PSTR ("Cannot open file \"%s\"\r\n"), fileName);
      return;
    }
  }

  for (timeReadStart = millis (), bytesRead = 0, i = 0; true; i = (i + 1) & 3)
  {
    char c;
    int l;

    if ((l = w [i].read (&c, sizeof (c))) < 0)
    { 
      monitorPrintf_P (PSTR ("Cannot read file %d\r\n"), i);
      return;
    }
    else if (!l)
    { 
      w [i].close ();

      if (i == 3)
        break;
    }
    else
    { 
      bytesRead += l;

      if (c != ('A' + i))
      { 
        monitorPrintf_P (PSTR ("Eeek!  Didn't get expected character.  Wanted %c, got %c on file %d\r\n"), 'A' + i, c, i);
        return;
      }
    }
  }

  timeDone = millis ();

  monitorPrintf_P (PSTR ("read  time  : %lums\r\n"), timeDone - timeReadStart);
  monitorPrintf_P (PSTR ("total bytes : %lu\r\n"), bytesRead);
}
  
Code: Delete old files before write
 
static void monitorCmdTestCleanup (void)
{
  char fileName [DOS83_FILENAME_SIZE];

  strcpy_P (fileName, PSTR ("TESTFILE.000"));

  for (int i = 0; i < 4; i++)
  {
    fileName [sizeof (fileName) - 2]++;
    storageGetRoot ()->remove (storageGetRoot (), fileName);
  }
}