I was curious about the difference in execution time between NIF
and WebAssembly
.
- NIF with
Zigler
server-side - WASI with
Wasmex
server-side - WASM with native
WebAssembly
in the browser
And the winner is NIF, about two times faster.
All Zig
code compiled ReleaseFast
in all cases.
Zigler
compiles theZig
for you- you pass the target
.wasi
inzig build
forWASI
- you pass or
.freestanding
inzig build
forWebAssembly
.
The main difference between the two solutions is the way you pass data to and from the host.
- with
Zigler
, you can receive a struct as a map.Zigler
is quite impressive. It also provides also resources: a (safe) way to return pointers to native data structures from aNIF
. - with
Wasmex
orWebAssembly
, you receive a memory index when the data is in binary form
When using WebAssembly, the crux of the interaction is the serialisation of the data structure in Zig you want to pass to Elixir in order to fit the linear memory model. It remains to pattern match on the binary on the Elixir side.
- with
Zigler
, you pass an array of data - with
Wasmex
orWA
, you write data to an index that has been allocated byZig
. This way, you can pass strings or serialised data.
The results of the Genetic Algorithm ran 32 times to collect stats back in Elixir are:
# Threaded NIF
iex(24)> :timer.tc(fn -> GAThreaded.calc(10, "I want a beer! What about you?!") end)
{2_624_277,
%{max: 1733, min: 545, elitism: 10, mean: 969.25, std_dev: 467.44238906711723}}
# unthreaded NIF
iex(26)> :timer.tc(fn -> GAStd.calc(10, "I want a beer! What about you?!") end)
{2_038_405,
%{max: 1926, min: 537, elitism: 10, mean: 994.46875, std_dev: 347.26}
# WASI
iex(29)> :timer.tc(fn -> GAWasi.calc(10, "I want a beer! What about you?!") end)
{4_613_034,
%{max: 1532, min: 474, elitism: 10, mean: 894.25, std_dev: 264.93, trials: 32}}
4_952_399,
{
elitism: 10,
mean: 1086.98,
stdDev: 451.45,
min: 428,
max: 2668,
trials: 32,
};
You need to pass a string from the host (Javascript) to the WebAssembly container, and recover data from the WAC.
- you call a Zig function that allocates memory of a given length
- you populate the WA memory at this position (via an ArrayBuffer given by TextDecoder with "ascii" encoding)
- you call a Zig function to read the memory at this position and populate an internal variable
- to recover data from Zig, you need to serialise it into a linear format
- you call a Zig function that sends you the index of this data
- you populate a DataView at this index and convert the data