I was curious about the performance of posix_spawn, so I wrote a test
program to experiment with, attached to this message.

Here is my testing:

     # Using fork + execvp to execute 'true' 10000 times.
     $ gcc main.c
     $ perf stat --repeat 5 ./a.out 2>&1 | grep -F 'seconds time elapsed'
            5.8890 +- 0.0761 seconds time elapsed  ( +-  1.29% )

     # Using vfork + execvp
     $ gcc -DFORK=vfork main.c
     $ perf stat --repeat 5 ./a.out 2>&1 | grep -F 'seconds time elapsed'
            4.4907 +- 0.0596 seconds time elapsed  ( +-  1.33% )

     # Using posix_spawnp
     $ gcc -DUSE_POSIX_SPAWN=1 main.c
     $ perf stat --repeat 5 ./a.out 2>&1 | grep -F 'seconds time elapsed'
            4.6260 +- 0.0508 seconds time elapsed  ( +-  1.10% )

It seems like this is due to the different clone flags:

     # fork + execvp
     clone(child_stack=NULL, 
flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, 
child_tidptr=0x7ff8b35f6a10)

     # posix_spawnp
     clone3({flags=CLONE_VM|CLONE_VFORK|CLONE_CLEAR_SIGHAND, 
exit_signal=SIGCHLD, stack=0x7f9978d00000, stack_size=0x9000}

The vfork man-page says that it uses "CLONE_VM | CLONE_VFORK | SIGCHLD"
[1].

How about using posix_spawn in coreutils where possible (that is, not
'env' which does not fork)?

The micro-optimization could add up for for 'install -s'. It would also
help port to Windows, which does not have fork and vfork. The code in
src/split.c also looks like a good candidate for
posix_spawn_file_actions_adddup2, etc. [2].

Collin

[1] https://man7.org/linux/man-pages/man2/vfork.2.html
[2] https://pubs.opengroup.org/onlinepubs/9699919799/

#define _GNU_SOURCE 1
#include <stdio.h>
#include <spawn.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#ifdef __APPLE__
# include <crt_externs.h>
# define environ (*_NSGetEnviron ())
#endif
#ifndef FORK
# define FORK fork
#endif
int
main (void)
{
  char *const spawn_argv[] = { "true", NULL };
  for (int i = 0; i < 10000; ++i)
    {
      pid_t child;
#ifdef USE_POSIX_SPAWN
      int result = posix_spawnp (&child, spawn_argv[0], NULL, NULL,
                                 spawn_argv, environ);
#else
      int result = 0;
      child = FORK ();
      switch (child)
        {
        case -1: /* Fork failed.  */
          abort ();
          break;
        case 0: /* Child.  */
          if (execvp ("true", spawn_argv) < 0)
            abort ();
          break;
        }
      if (child == 0)
        continue;
      /* Parent.  */
#endif
      if (result != 0)
        abort ();
      int status = 0;
      while (waitpid (child, &status, 0) != child)
        ;
      if (! WIFEXITED (status) || WEXITSTATUS (status) != 0)
        abort ();
    }
  return 0;
}

Reply via email to