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