Spawning Cat
Saturday, September 26, 2026
I’ve had fun watching the Bun project over the years. It has been neat in particular to see the efforts they have made to improve the performance of the JavaScript ecosystem. One of the main voices in that effort has been Jarred Sumner, who shared a small JavaScript program a bit more than a year ago:
Jarred Sumner on spawning cat
This benchmark repeatedly starts cat in batches of 100 processes. Each child
reads the JavaScript source file, with standard input, output, and error redirected
to /dev/null. There is no shell involved, and each batch waits for all of
its children to exit before starting the next one.
I was curious how Factor would perform and decided to benchmark this on an Apple Silicon Mac, taking advantage of our recently stabilized Native ARM64 builds.
Unfortunately, the answer was not good!
It took 5.445 seconds to run a Factor equivalent benchmark, compared to the 1.936 seconds it took to run the 10,000 process JavaScript benchmark in Bun 1.4.2.
Yikes!
I was, however, able to make a few improvements to Factor’s process launcher:
- Inherit the existing environment directly when there are no overrides.
- Have the discarded standard streams share one temporary
/dev/nulldescriptor. - Check
PATHcandidates before callingposix_spawn, with a fallback toposix_spawnp. - Use kqueue’s
SIGCHLDnotifications to wake the waiting thread when children exit.
I’m excited to say that we now run the “spawning cat” benchmark at about the same speed, on an Apple Silicon Mac, launching and waiting for 10,000 processes takes about two seconds:
| Runtime | Time |
|---|---|
| Factor 0.102 | 1.951 seconds |
| Bun 1.4.2 | 1.936 seconds |
This is available in the development version of Factor.