I built an interactive Mandelbrot set visualizer in Rust that paralellizes computation with Rayon and compiles to WebAssembly to run in your browser. Explore the Demo Here (Works on a recent version of Chrome.)
Or follow one of the direct links into particular areas of the mandelbrot world map below:
Rayon in WebAssembly
Rayon’s core abstraction is the ability to spawn parallel computations and join them in the end to aggregate their results. On top of these
rayon provides very convenient work-stealing parallel collections that make parallelising iterator-based code very easy.
There is a catch though:
rayon usually relies on native threads provided by the std but these APIs are stubbed out in
wasm32-unknown-unknown target since, as the target name indicates, the host envirionment information is missing and we cannot assume anything about its threading APIs.
We can solve this by manually providing multithreading primitives to
rayon. This is is supported in recent versions by a handler that helps to set the function called for spawning new threads.
That gives a mechanism for multithreading but WebAssembly still does not have threads: we can have the new spawn handler to run everything in a single webAssembly module but that would defeat the purpose of using
rayon in the first place.
hack threads into rayon
Instead, we can use a hack to provide threads to WebAssembly. Most modern browsers now support Web Workers API which act more like separate processes than threads but will still do the job. The idea is to spawn several such
Workers that act as a thread pool for rayon. These threads in the pool simply wait for incoming messages, interpret the first message as a WebAssembly module, deploy it, and on the following messages invoke functions from the already deployed WebAssembly module. This uses very new and fragile APIs and
raycast-parallel in wasm_bindgen crate is the only working demonstration of the method which is what I am relying on.
Then we spawn all of multithreading logic to one worker that will then use rayon’s parallelism to send work to all the others. This helps to keep the main thread free and interactive for the user.
thread_pool.install is rayon’s way of saying instructing a close to use a custom pool instead of the native OS one (in this case using the native OS one would crash since rust’s std is stubbed out in wasm32-unknown-unknown)
Then we create a Rust
Pass more complex structures as a result
However, we do not have to limit ourselves to computing such primitive results. We can pass more complex JS objects using
ImageData object that can directly be rendered in a canvas element. In the next section we will do exactly that.
Sharing memory among workers
Copying buffers from workers to the main thread would defeat the purpose of parallelism and would probably be slower than the equivalent sequential program. For that reason the code represents the destination canvas data as a shared array buffer. This feature, however, is very new and still not available in stable Mozilla Firefox that is why the demo requires a recent Google Chrome.