Re: Factor

Factor: the language, the theory, and the practice.

Spawning Cat

Saturday, September 26, 2026

#performance

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:

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/null descriptor.
  • Check PATH candidates before calling posix_spawn, with a fallback to posix_spawnp.
  • Use kqueue’s SIGCHLD notifications 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.