1

I am curious why the performance of my logging routines (using O_APPEND) are so poor on MacOS, and did some testing. The performance is quite bad. Is this a known problem with APFS?

These tests are on a Mac Mini 2018 (Intel) running Sonoma 14.5.

(a) Internal SSD formatted as APFS. (And the performance is worse if the file already exists!)

bll-mac:/Volumes/Users/bll/bdj4/src$ rm -f tt-out.txt; time $HOME/bdj4/src/tt & time $HOME/bdj4/src/tt &
real    0m11.294s
user    0m0.073s
sys     0m6.144s

real    0m11.391s
user    0m0.143s
sys     0m12.304s

(b) /Volumes/spare is an External HDD formatted as HFS. (system info says the protocol is USB).

bll-mac:/Volumes/spare$ rm -f tt-out.txt; time $HOME/bdj4/src/tt & time $HOME/bdj4/src/tt & 
real    0m4.223s
user    0m0.058s
sys     0m2.681s

real    0m4.241s
user    0m0.057s
sys     0m2.680s

(c) On the same external HDD on an APFS partition.

bll-mac:/Volumes/Avail-big$ rm -f tt-out.txt; time $HOME/bdj4/src/tt & time $HOME/bdj4/src/tt &
real    0m10.238s
user    0m0.136s
sys     0m11.497s

real    0m10.239s
user    0m0.203s
sys     0m17.234s

(d) Just for comparison, on Linux, on an SSD (SATA).

bll-g7:bll$ time ./tt & time ./tt &
real    0m2.213s
user    0m0.033s
sys     0m1.200s

real    0m2.227s
user    0m0.047s
sys     0m1.228s

(e) And on MacOS, the internal SSD with O_APPEND off: The speed here is more reasonable, though twice as slow as Linux.

bll-mac:/Volumes/Users/bll/bdj4/src$ rm -f tt-out.txt; time $HOME/bdj4/src/tt x & time $HOME/bdj4/src/tt x &
real    0m2.316s
user    0m0.039s
sys     0m1.119s

real    0m2.331s
user    0m0.118s
sys     0m3.339s

Code:

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

enum {
   LCOUNT = 100000,
   TCOUNT = 8,
};

int
fileSharedOpen (const char *fname, int appflag)
{
  int         fd;
  int         flags;

  if (fname == NULL || ! *fname) {
    return -1;
  }

  flags = O_WRONLY | O_CREAT;
  if (appflag) { 
    flags |= O_APPEND;
  }

  fd = open (fname, flags, 0600);
  return fd;
}

ssize_t
fileSharedWrite (int fd, const char *data, size_t len)
{
  ssize_t rc;

  rc = write (fd, data, len);
  return rc;
}

void
fileSharedClose (int fd)
{
  close (fd);
  return;
}

void
process (int flag)
{
  int     fd;
  char    tdata [400];
  size_t  len;

  strcpy (tdata, "aaaaaaaaaaaaa");
  strcat (tdata, "bbbbbbbbbbbbb");
  strcat (tdata, "ccccccccccccc");
  strcat (tdata, "\n");
  len = strlen (tdata);
  
  fd = fileSharedOpen ("tt-out.txt", flag);
  for (int i = 0; i < LCOUNT; ++i) {
    fileSharedWrite (fd, tdata, len);
  }
  fileSharedClose (fd);
}

int
main (int argc, char *argv [])
{
  int       flag;

  flag = true;
  if (argc > 1) {
    flag = false; 
  }

  for (int i = 0; i < TCOUNT; ++i) {
    pid_t   pid;

    pid = fork ();
    if (pid != 0) {
      process (flag);
      exit (0);
    }
  }
  process (flag);
  return 0;
}
9
  • Could it be that APFS is faster using specific macOS APIs, rather than the standard ones?
    – benwiggy
    Commented Jul 31 at 14:06
  • @benwiggy That is certainly possible, though I think unlikely. I don't know that anyone will have an answer for this question, and the next question will be how to code a logging function that appends to a file without the performance hit. I'll see if there are any native MacOS APIs that can be used as you suggest, and also try using a semaphore to control access to the log file(s).
    – Brad Lanam
    Commented Jul 31 at 14:11
  • Code level questions generally do better on stack overflow. Do you want us to keep this here or migrate it?
    – bmike
    Commented Jul 31 at 15:24
  • @bmike I will make a new code-level question on stackoverflow later. This current question is more of a high-level, why-in-the-heck is APFS performance so bad.
    – Brad Lanam
    Commented Jul 31 at 15:35
  • 1
    You might need to change your code to measure the io performance to properly pin the blame on APFS and how the compiler generates io code. My hunch is what you are actually measuring is gatekeeper checking your unsigned app. If you comment out all the file io, you can time that and do some math on the actual io if you don’t want write code to trigger a timer once the app launch is complete but before the io starts.
    – bmike
    Commented Jul 31 at 16:18

0

You must log in to answer this question.

Browse other questions tagged .