Using Rust with Elixir for code reuse and performance
blog.doctave.com> Unsurprisingly, Rust outperforms Elixir by 2 orders of magnitude.
That seems very surprising to me. Is this a common result? I've heard that the BEAM is not the best for "number crunching" code, is this one of those scenarios? Is this really just the raw performance of the languages, or a difference in algorithms? There's also no mention of the version of Elixir and the OTP. Did the JIT change anything?
Heavy duty string bashing is a pessimal case for BEAM, yes. The two core data structures you could use for this, the binaries or the 'strings' (which are linked lists of characters), each have their own problems with this sort of algorithm. Erlang was designed to fling chunks around, maybe take a header apart before deciding where to fling a chunk around, not to grovel over every byte of some fairly-large string for detailed parsing.
Based on performance numbers I've seen, I'd expect a one-order-of-magnitude difference to be a more common case in general.
For me, Elixir with Rust NIFs seems like a match made in heaven.
The BEAM VM is amazing at many things but it is still a VM and while the BeamAsm Just In Time compiler added in 2020 can offer performance improvement gains of 130%, there are areas where the BEAM still won't outperform native code.
Erlang excels at network programming and binary data processing. Computation intensive math and heavy string processing are both good NIF use cases.
An article on string performance [1],pre-JIT, describes initial Elixir benchmark of 140s vs C's 3.74s. They managed to make a number of tradeoffs and get the Elixir benchmark improved to 13s. Part of how they did that was to not use unicode (IO.binstream instead of IO.stream), that alone gained them ~4s.
[1] https://blog.jola.dev/elixir-string-processing-optimization
I wonder if they looked into using https://github.com/asaaki/cmark.ex which is an already made Markdown Elixir NIF written in C. No glue code needed since the package already exists.
Back when I was writing Elixir, it's what I used to process Markdown and it was also substantially faster than the native Elixir Markdown library (Earmark).
Author here. I actually was not aware of cmark.ex - thanks for pointing it out.
In this case the code reuse was more important than pure native speed. We already had a Rust library that used pulldown-cmark [1] with some custom tweaks that we wanted to duplicate. Maybe this behavior could have been copied using cmark.ex too (we thought about doing this in pure Elixir, as mentioned in the post), but given how straightforward Rustler made integrating our existing code, this seems like the better choice.
Before Rust, it seemed there was a law that every sufficiently critical code path would eventually be rewritten in C or C++.
After Rust, giving up a bit of performance to not have to maintain the C code underneath seems preferred. And often you don't even sacrifice performance.
> After Rust, giving up a bit of performance to not have to maintain the C code underneath seems preferred
Yeah no doubt about it, although in this case the C implementation has been a long running project that's under the official commonmark GitHub repo at https://github.com/commonmark/cmark.
But I think the most important thing here is an Elixir NIF already exists to use it. The blog post as is leaves readers having to implement ~100 lines of Elixir code to use the Rust version because the author of the blog post didn't include that code in the article, or open source it as a library for others to use.
From a reader's POV if your goal is to get a highly stable, fast and safe Markdown parser running in Elixir, the Elixir cmark library I linked in a parent comment solves that problem out of the box.
Yeah; I think people responding are taking your response as "they did the wrong thing". It sounds more like "Hey, here's another option that I've used in the past to solve that problem", leaving the evaluation of the relevant tradeoffs as an exercise to the reader (where it should be since you'd wait them differently for different projects)
Yea, I'd probably of ended up using cmark too.
It is nice though to learn about NIFs and rustler, there are a lot of high quality crates out there that you can wrap and take advantage of from elixir code once you get comfortable with doing it.
So possibly a sub-optimal approach, but the author indicates in another comment they had customized the rust version and wanted to be able to share code, which seems legit. Always good to stock up that toolbox though with more neat stuff, and rustler NIFs are seriously neat.
I’ve seen a major preference toward Rust for NIFs in Elixir land because of the guarantees. People like their BEAM guarantees and exiting the BEAM without strong guarantees feels risky.
Even if they did they may have preferred Rust because parsing arbitrary user provided input is one of the more risky things you can do in C. This is a perfect use case for Rust's memory safety model.
One thing to keep in mind is that even the simplest Rust NIF will significantly slow your builds and increase your repo/artifact sizes.
Author here. This is a good point. In this case we found it does slow down clean builds somewhat since we're pulling down + compiling Rust dependencies now as well as Elixir. While actually editing Elixir code I haven't experienced any noticeable slowdown for incremental builds - the Rust code is mostly static.
It's one reason I prefer lighter languages for NIF's. My personal preference is to use Nim and Nimler [1]. It generally compiles quicker than Rust while providing most of the same benefits. To be fair compiling a small Rust library doesn't take too much time. Especially compared to the performance numbers from those Rust NIFs!
It’s also important to note that a NIF can bring down your whole BEAM.
It can but it's far less likely with Rust, that's the beauty of it.
For someone who does not know much about NIFs, will it be a lot slower if the Rust service is coded as a command line application and then invoked using System.cmd? Is that a good alternative?
It will be faaaar slower. If it is a good alternative depends on your need. Benchmark your situation
Thank you.