simd-prng
My latest rabbit hole resulted in the creation of simd-prng. It is a dead simple JavaScript PRNG library with a small twist: it is backed by WebAssembly[1] and it uses SIMD instructions for extra performance. Its initial codename was YAMT ("Yet Another Mersenne Twister"), but I've settled for a far more boring name.
The inner dialog during its inception was something like:
‐ I want a seedable PRNG for a web thing
‐ Ugh, I can't tell what looks good on NPM (and it's not huge bundle-wise)
‐ Hmm, maybe I can just wrap Rust's
randin a small WebAssembly payload‐ Ugh, that's too big,[2] maybe I'll just use a JS PRNG
‐ I bet you can write something really fast and small using AssemblyScript
‐ Oh, neat, AssemblyScript has SIMD intrinsics
‐ Oh, neat, people have written fast PRNGs leveraging SIMD
‐ How hard would it be to port this to AssemblyScript?
‐ Ugh, I'm too lazy. Emscripten, you are my only hope!
Emscripten is very versatile, so it took a bit until I tuned it to my taste:
- I did not want any JS glue code, just a simple Wasm module I could wrap manually. "Standalone" Wasm modules is the name of the game here, but apparently they're not quite production-ready.
- I wanted to minimize heap usage, so I got the allocated memory down to 3 MiB. The default behavior of Emscripten is to forbid dynamic memory growth, so this means that there is a hard limit on the number of generators that can be created. Not too bad, considering that most likely you only need one.
- I cannot avoid an allocator[3] but I did choose
emmallocto reduce code size. That got me down to 4 KiB of uncompressed Wasm payload. Neat!
As I mentioned, I wrote the glue code myself, as I wanted to make sure it was idiomatic and easy to consume in all JS runtimes, including the browser. But it turns out that there is not much of an "idiomatic" way of exposing a WebAssembly-backed JS package for browsers. Inlining the Wasm blob might be reasonable, especially given its size in this case, but I dislike it. I would prefer consumers to have maximum flexibility to choose between different strategies, so I don't impose any particular pattern onto their application.
That means that the module has to fetch the Wasm code dynamically. But, alas, the most popular bundler (Vite) does not like dependencies fetching assets during runtime in local dev. I ended up with 3 entrypoints in the library: one for Node.js and compatible runtimes, one specific for Vite and a last one that leverages native ESM imports of WebAssembly.
Hey, what about the PRNG
Ha, I know next to nothing about PRNGs. SFMT is just something
I stumbled upon and used as base. I presume it is better than Math.random() so... mission acomplished?
I haven't even researched how fast it really is 🤦 I will do some proper benchmarking at some point, but my informal testing
shows it is better than a few PRNGs I found on npm. Heck, I just compared it to Math.random() and it looks ~50% faster!
That's it, go check it:
repository ‐ docs ‐ npm package