g_spawn_*() is extremely slow under certain circumstances
g_spawn_*()
is extremely slow under the following circumstances:
- the operating system is neither a Linux distribution nor a Solaris derivative
-
RLIMIT_NOFILE
is unlimited -
g_spawn_*()
is called withoutG_SPAWN_LEAVE_DESCRIPTORS_OPEN
Then the function fdwalk()
in glib/gspawn.c
will call
fcntl(2)
for every possible file descriptor, which if
RLIMIT_NOFILE
is unlimited can be a very large number. On my
system (FreeBSD 12) this results in 941256 fcntl(2)
syscalls which
take 1-2 seconds in total. This is of course a rather undesirable
duration just for spawning a new process.
This is not just a hypothetical problem. I ran into it when using the
awesome window manager. Its awful.spawn()
function calls
g_spawn_async_with_pipes()
for launching other programs. By
default regular login sessions on FreeBSD have no predefined
RLIMIT_NOFILE
. Therefore when launching another program from
awesome on FreeBSD one encounters a delay of 1-2 seconds due to this
problem. psychon on #awesome narrowed it down to g_spawn_*()
by
providing the following test program test.c
:
#include <glib.h>
#include <string.h>
int main(void)
{
gchar *argv[3];
argv[0] = strdup("/bin/echo");
argv[1] = strdup("I live!");
argv[2] = NULL;
g_spawn_sync(NULL, argv, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL);
return 0;
}
Compiling and running it yields the following result:
$ freebsd-version
12.0-RELEASE-p1
$ pkg query %v glib
2.56.3_2,1
$ ulimit -n
941256
$ cc -O2 $(pkg-config --cflags --libs glib-2.0) test.c
$ command time ./a.out
I live!
2.53 real 0.85 user 1.67 sys
$ truss -f ./a.out 2>&1 | grep -c fcntl
941256
From this we can clearly see that it's g_spawn_*()
that's causing
the problem for awesome. g_spawn_*()
can probably be optimized to
not perform almost one million syscalls.