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); } } |