fork(even with CoW pages) can be extremely slow with modern memory usages due to the antiquated default page size of OSes because it requires copying the page table.
For example, a program using 400MiB of memory with 4KiB pages requires copying 102,400 page structs. On Linux, a page struct in the kernel is at least 72 bytes so this requires a copy of 7.2MiB.
This scales linearly with heap size, after some personal testing on Linux I found forking with almost nothing allocated to take ~60 microseconds, 100 MiB ~3 milliseconds, and 1GiB to take 30-45 milliseconds. vfork took a constant 20 microseconds no matter the heap size.
Solution: where available, use posix_spawn, vfork, etc. These do not require copying page tables. std.process does not require the page tables since it immediately replaces itself with another process.
I marked this all OSes, but really it's all POSIX OSes I guess.
Comment #1 by schveiguy — 2015-07-06T12:46:36Z
CC'ing Lars, this is a good idea.
Comment #2 by dsp — 2015-07-07T05:52:14Z
https://github.com/D-Programming-Language/phobos/pull/3476
I opted for using vfork. It seems we are in all cases doing the right thing, not tempering with the process space and correctly calling execv* or _exit.
It seems that the "correct" method would be using posix_spawn, however we need support for this in druntime, so at the moment, I think going with vfork is less invasive while providing the proposed enhancements.
Comment #3 by post — 2015-07-31T07:21:56Z
I think the "correct" thing to do here (on Linux, at least) is to use the clone() function, as described in this article:
http://blog.famzah.net/2009/11/20/a-much-faster-popen-and-system-implementation-for-linux/
NOTE: If anyone wants to take a stab at implementing this for Phobos, don't look at the actual source code for the "popen-noshell" library, as it is LGPL-licensed.
Both clone() and vfork() are GNU library wrappers around Linux' clone system call, but clone() allows a much finer control over which data gets copied into the new process. Unlike vfork(), however, clone() is not a drop-in replacement for fork(). It takes a function to execute and a stack space in which to execute it, as opposed to continuing execution from the same point in the child process. In other words, there's a bit more work needed to incorporate it in spawnProcess().
Note that vfork() seems to be generally frowned upon; see for example the NOTES section of the man page:
http://man7.org/linux/man-pages/man2/vfork.2.html#NOTES
Maybe have a branch for linux and call clone there, call posix_spawn or fork for other systems. vfork looks scary and posix deprecated it.
Comment #7 by kubo39 — 2018-08-02T15:54:49Z
(In reply to anonymous4 from comment #6)
> Maybe have a branch for linux and call clone there, call posix_spawn or fork
> for other systems. vfork looks scary and posix deprecated it.
If we use clone(2), the stack of the child process is newly allocated and the stack of the parent process is not shared. It's preferred.
Maybe I should add clone(2) to druntime before...
https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=9ff72da471a509a8c19791efe469f47fa6977410
Comment #8 by robert.schadek — 2024-12-01T16:24:47Z