Zigler: zig nifs for Elixir

vscode support coming soon (after I figure out azure devops)

4 Likes

Talk has been postponed to SF elixir/erlang February due to difficulty of finding a venue for this month!

@ityonemo :+1:

yeah, sorry the talk got pushed back and then it ran into codebeam, because TheRealReal wanted to give a talk, then covid happened, so…

also I’m slammed at work, so the new release is about 1.5 weeks out perpetually for the next two more weeks at least. =/

2 Likes

https://hexdocs.pm/zigler/0.3.0-pre/Zigler.html

Version 0.3.0-pre has landed with support for Zig 0.6.0. Support for “Long Nifs” which are Nifs that run in their own OS thread is currently disabled (pending investigation of most effective use of Zig async methodology). Other major improvements include a completely overhauled Zig Parser and support for Resources. Full, correct documentation and fixing long nifs will be released with the proper 0.3.0 release, but I wanted to spin up a few demos that leverage the Zig 0.6.0 standard library first.

The couple of demos I intend to write are:

  • ICMP ping library for elixir
  • Fast, safe (flushes the JSON binary from the shared binary memory) JSON parser using arena allocation and builtin BEAM allocator.

I would like to compare the performance JSON parser to an equivalent package made with rustler, so if anyone knows of the preferred Rust JSON parser that would be appreciated. Or, if anyone would like an easy demo, I can try my hand at it.

Things to look forward to:

I’m thinking about releasing Nerves support in the v0.3.0 series where the native cross-compilation capabilities of Zig are used to allow you to seamlessly cross-compile NIF modules in to your nerves releases.

6 Likes

(a very sketchy) ICMP library has been completed: Zing, basic Elixir ICMP ('ping') server using Zig NIF

1 Like

Zigler 0.3.0 is out. It’s validated to work with releases! I also eliminated the “get_zig” mix task, so it will automatically download zig for you. And also, it works with Nerves, performing cross-compilation to ARM binaries out of the box. https://hexdocs.pm/zigler/

2 Likes

Hmmmmm I wonder if you think zigler would be a good approach to writing NIFs that wrap C libs - given that you’ve made the NIF part easy and Zig can call C libs without any FFI getting in the way.

It’s in there in the docs “Zig is likely to be an easier glue language for C ABIs than C.”. :wink:

Thanks much, yes, I should have read the docs. I know what my weekend project is now at least :slight_smile:

The tests contain an example where I call a BLAS function. This is an area where it’s kind of hard to test and hard to document so if you find a problem let me know!

Rust’s main JSON parser would definitely be the one on serde, that doesn’t mean it’s the fastest (although it is very fast), but it is practically codeless to use it on the Rust side. ^.^

Ooo, nice!

2 Likes

Rust’s main JSON parser would definitely be the one on serde, that doesn’t mean it’s the fastest (although it is very fast), but it is practically codeless to use it on the Rust side. ^.^

I take it that means that rustler automatically converts rusts’ map type to erlang’s? so you have to first serialize into rust, and then reserialize into BEAM?

Rustler will automatically serialise most Erlang terms to the Rust code. The reverse is also true. There are exceptions but it’s really easy to specify how should they be serialised as well.

In terms of programmer ergonomics Rustler is quite good (but I do mean the version that’s currently in a release candidate state; the one before it required a good amount of boilerplate for you to write).

So yeah, you’d have Rustler’s lightweight serialisation between the BEAM and the Rust code (on top of serde_json library’s run time). I’d imagine it’d still be miles faster than Jason though – but this should be tested.

There’s a library that seems to do that btw: GitHub - thomas9911/data_serialize: serialize elixir maps to other formats using rust's serde. Not sure if the author dwells here on ElixirForum but it should be pretty easy to use it and benchmark against your lib.

1 Like

holy mother of pearl. Zig has generic hashmaps, so I think it would be pretty easy to write a shim with the builtin beam map functions and just plug it in to the std library json parser.

1 Like

Oh yeah, I remember that, I think the author has popped up on here actually…

There is also this too, it’s a serde interface for converting to/from ETF’s in rustler if you don’t want to use the normal rustler interface, I think it had a tiny bit of overhead compared to using them raw but I bet that can be fixed:

And don’t forget about the hex library jiffy, it’s a C NIF that binds to some json library to do things there too, it would be a good comparison as well. Since zig can compile C as well I’ve no doubt it could go faster.

Don’t forget to use dirty NIF’s for json processing though!!!

2 Likes

Which kind of them though? Rustler can only mark a function as “dirty I/O” or “dirty CPU” but not both… :confused:

Dirty CPU, if you use michal’s benchmarking in Jason. Elixir takes care of the binary loading. I might try doing it as a “long” nif, which uses code that spawns an os thread using the built-in BEAM adapters for that (currently broken in 0.3.0), which has the advantage that it doesn’t consume a dirty nif scheduler, but it might take longer for the OS to do the thread creation.

1 Like

Yes, Dirty CPU. Dirty I/O means you won’t be doing much processing so can be evicted. Dirty CPU means you will be consuming at least a good chunk of CPU, regardless of anything else you are doing.

But then you still need to synchronize some other way, it’s far far better to just use a dirty nif scheduler, they are very cheap now in recent OTP versions and doing custom thread handling should only be done in certain extremely specialized cases.

1 Like

But then you still need to synchronize some other way.

If you choose to do a ‘long’ nif, Zigler wraps your function into two nifs, one which launches the process and wraps the frame information into a resource. The second nif retrieves the resource. The first nif’s wrapping function also shoots a message back to the calling process; this is intercepted on the OTP side, which calls the second nif, so indeed the synchronization is taken care of for you.