An optimization to help code compilation times on big CPUs
phoronix.com
phoronix.com
[1] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/lin...
Chances are, you’ll learn something new.
/* 64 processes */
fork(); fork(); fork(); fork(); fork(); fork();
I can totally see most people, myself included, writing a dozen of lines instead, with a for loop, checking for parent/child/error return code, etc. Which might be preferable for some code somewhere deep inside the app logic that will potentially run on a resource-constrained machine. But for the explanation code to be read by a human, this one-liner is such a good choice!> In particular, the GNU make jobserver code ends up using a pipe as a way to limit parallelism, where each job consumes a token by reading a byte from the jobserver pipe, and releases the token by writing a byte back to the pipe.
Umm, that's what semaphores are for.
But… if I could, I would probably use POSIX semaphores, no wait SYS-V, no, hang on, let me read some web pages… um… let me check my repositories and see which I've used before… both… hmm…
Seriously, I can't see getting so frustrated choosing that I'd just go with pipe(), but there is room for improvement in portable semaphore APIs.
That said, pipe() is not "portable" either, so whether to use platform-dependent code for pipes or semaphores... no big difference.
> Seriously, I can't see getting so frustrated
If reading documentation and evaluating options frustrates you, you shouldn't be dealing with software development.
pipe() is in POSIX base since forever. If it is not portable, I don't know what is. Oh well C is more portable than POSIX, ok; but C does not do IPC at all (actually not even processes) so that's irrelevant. SysV semaphores are in XSI, POSIX semaphore are only in base since recently, previously they were in a Semaphore option.
So yes, pipe() is absolutely more portable. Works on all BSDs, GNU/Linux, embedded Linuxes, Solaris, dead Unixes, Android, QNX, Cygwin (where on the other hand semget seems buggy: https://cygwin.com/ml/cygwin/2019-08/msg00099.html), Mac Os, etc.
Alas, the twain do not mix. So anything that wants to interoperate has to know what family of jobserver it is interoperating with. (It has been well known in the Unix world for years that if all programs do not agree on whether a shared file descriptor is in non-blocking mode, difficulties ensue. Yes, nonblock_read() and nonblock_write() would have provided a way to solve this whilst retaining interoperability.)
* https://jdebp.eu/FGA/dont-set-shared-file-descriptors-to-non...
C11 adds threads and mutexes
The only way to properly solve missing dependencies is to hide targets unless they are explicitly depended on. The only systems I know that do that are Nix and Bazel (and its clones).
If make knows about all the inputs and outputs of every rule, it can make sure dependencies are built before they are used. The problem occurs when the makefile doesn't tell make about every dependency.
Also the rule scripts themselves can have race conditions. A trivial example is if they end up using the same temp filename when doing work.
For example if you have two targets, A and B, and B depends on A's output but you forgot to say so, then `make` can build B before A even if you don't use the `-j` option.
So if you have:
all: A B
Then if both need a rebuild A is always first, except when -j breaks that.
But I can't say I have played close attention to every time I have typed make.
There are a number of python libraries I can’t install on my laptop easily because the native code runs out of physical (4GB!) memory spawning compiler jobs and everything starts thrashing, it’s just dumb to go forking without asking the user first. People who really want it can give make the -j flag.
Manually specifying resources doesn't work well either because it uses 2x+ the resources you allocate it.
Especially painful when one of your coworkers has a beefier machine than you and changes the number of parallel jobs offhand one day, causing your computer to freeze until the OOM killer gets the right build process.
I think this case is a bit different, in that the bug already triggers sometimes without this change, the change just makes it happen more often. Which is why the GNU Make bug report predates this improvement.
Edit: crap, that autocorrected on my iPhone - that should have been decrease!
[1] Having hacked on GNU make I know this won't be easy (it's quite a mess). In fact, it would probably be easier to replace it entirely and fix some of its other deficiencies in the process.
There are already dozens of alternatives to GNU Make. Linux already did something similar with Git.
Plus it will not only optimize GNU Make workloads, but potentially other programs that do that. And to be honest, playing with pipe for signaling (and more rarely token counting) is STILL a must-have way to do IPC portably, esp. if you want to integrate with polling on fds. Various OSes have also various new things, but they are non-portable -- maybe someone should try to get eventfd&friends in POSIX?
Anyway, if that's a "good example of how we develop software these days", I would say we managed to develop software the right way at last, making basic stuff work well before succumbing to the tentation to rewrite everything all the time just to play with new shinny techs, getting fresh bugs in the process.
Also you know what has evolved in the last few years, motivating this perf fix? Availability of HIGHLY parallel computers for not too expensive. Yes, I continue to find it highly refreshing that work is done to get all the boring historical software work better in that context. Maybe you think we can afford rewriting all the things all the time to follow supposedly radical technological changes (and using less well designed OSes?), but if so then this thought is not shared widely or at least not followed with action. Rightly so because economically the outcome would be doubtful: just look at the patch, it is way smaller than an hypothetical shiny new rewrite of GNU Make...
So, like buggy and hard to understand?