Arduino Mega 2560 SPI/SdFat Performance
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:
- Write a single file of 4K
- Read a single file of 4K
- Write 4 files of 4K, interleaving each byte read
- Read 4 files of 4K, interleaving each byte written
Note: The hardware SPI test numbers are not yet available
| 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 |
| 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);
}
}
|