Qqwy

Qqwy

TypeCheck Core Team

RustlerElixirFun: Calling Elixir from Rust

RustlerElixirFun

With Rustler, it became a lot easier to create well-behaving Natively-Implemented-Functions (NIFs).
However, one question which arises from time to time (here, here, here, here, …), is how to call back into Elixir code from within native code: If you pass an anonymous function to a NIF, then how can we use this function inside the NIF?

The short answer: This is not supported.
The longer answer: … But if you get ready to jump through some hoops, you can still make it work!

The gist of it: (Thanks to Erlang Forums user @robashton and GitHub user @tessi’s explanations for inspiration!)

  1. In native code, create a manual ‘future’ (a mutex wrapping a potentially-empty value, and a condition variable).
  2. Wrap this ‘future’ into a reference which can be passed back to Elixir.
  3. Send {fun, params, future_ref} to a particular process (or pool of processes) which you’ve been running.
  4. Block the native code until the future is filled. (Or a timeout is triggered)
  5. In the elixir GenServer, run the function, (handle errors) and once the result is obtained, call a second NIF with the result and the future_ref.
  6. This NIF will ‘fill’ the future with the passed result and immediately return.
  7. Now the original native code will continue.

Or, in a diagram:


Until now, there were a couple of projects which did this manually, but it seemed to make sense to start working on a library to abstract this pattern once and for all.
This way, we can make sure it works well (no edge cases) and as efficient as it might be, with both a single GenServer you might run, as well as a full-fledged pool.

The project can be found at https://github.com/Qqwy/elixir-rustler_elixir_fun.
Work on RustlerElixirFun is still ongoing, but feedback would already be much appreciated.

Most Liked

Qqwy

Qqwy

TypeCheck Core Team

A preliminary benchmark shows that calling a NIF which calls Elixir to run a trivial function which returns through the NIF back to the original caller takes roughly 85 times longer than calling a function directly in Elixir: 9.5µs vs 0.11µs.

I’m pretty sure that this overhead, while not neglegible, can still be considered ‘good enough’ for many projects, as the overhead of 9.5µs will often be overshadowed if there is some actual work (or communication with other processes or IO) going on inside the function.

$ MIX_ENV=bench mix run bench/call_overhead.ex 
Operating System: Linux
CPU Information: Intel(R) Core(TM) i7-6700HQ CPU @ 2.60GHz
Number of Available Cores: 8
Available memory: 31.18 GB
Elixir 1.12.0
Erlang 24.0.1

Benchmark suite executing with the following configuration:
warmup: 2 s
time: 5 s
memory time: 0 ns
reduction time: 0 ns
parallel: 1
inputs: 100, 100_000, 100_000_000
Estimated total run time: 1.40 min

Benchmarking apply(fun, param) with input 100 ...
Benchmarking apply(fun, param) with input 100_000 ...
Benchmarking apply(fun, param) with input 100_000_000 ...
Benchmarking apply_elixir_fun(Pool, fun, param) with input 100 ...
Benchmarking apply_elixir_fun(Pool, fun, param) with input 100_000 ...
Benchmarking apply_elixir_fun(Pool, fun, param) with input 100_000_000 ...
Benchmarking apply_elixir_fun(Server, fun, param) with input 100 ...
Benchmarking apply_elixir_fun(Server, fun, param) with input 100_000 ...
Benchmarking apply_elixir_fun(Server, fun, param) with input 100_000_000 ...
Benchmarking fun.(param) with input 100 ...
Benchmarking fun.(param) with input 100_000 ...
Benchmarking fun.(param) with input 100_000_000 ...

##### With input 100 #####
Name                                           ips        average  deviation         median         99th %
apply(fun, param)                           8.65 M       0.116 μs   ±144.37%       0.114 μs       0.178 μs
fun.(param)                                 8.64 M       0.116 μs   ±147.69%       0.114 μs       0.156 μs
apply_elixir_fun(Pool, fun, param)         0.104 M        9.63 μs   ±222.87%        8.54 μs       27.16 μs
apply_elixir_fun(Server, fun, param)       0.101 M        9.95 μs   ±233.02%        8.68 μs       27.38 μs

Comparison: 
apply(fun, param)                           8.65 M
fun.(param)                                 8.64 M - 1.00x slower +0.00003 μs
apply_elixir_fun(Pool, fun, param)         0.104 M - 83.27x slower +9.51 μs
apply_elixir_fun(Server, fun, param)       0.101 M - 86.01x slower +9.83 μs

##### With input 100_000 #####
Name                                           ips        average  deviation         median         99th %
apply(fun, param)                           8.55 M       0.117 μs   ±140.47%       0.115 μs       0.160 μs
fun.(param)                                 8.41 M       0.119 μs   ±131.56%       0.115 μs       0.180 μs
apply_elixir_fun(Server, fun, param)       0.104 M        9.59 μs   ±209.01%        8.53 μs       26.84 μs
apply_elixir_fun(Pool, fun, param)        0.0994 M       10.06 μs   ±179.97%        8.80 μs       27.30 μs

Comparison: 
apply(fun, param)                           8.55 M
fun.(param)                                 8.41 M - 1.02x slower +0.00198 μs
apply_elixir_fun(Server, fun, param)       0.104 M - 82.02x slower +9.47 μs
apply_elixir_fun(Pool, fun, param)        0.0994 M - 86.05x slower +9.94 μs

##### With input 100_000_000 #####
Name                                           ips        average  deviation         median         99th %
fun.(param)                                 8.42 M       0.119 μs   ±114.30%       0.115 μs       0.185 μs
apply(fun, param)                           8.27 M       0.121 μs   ±141.67%       0.118 μs        0.25 μs
apply_elixir_fun(Server, fun, param)       0.103 M        9.75 μs   ±219.29%        8.53 μs       27.54 μs
apply_elixir_fun(Pool, fun, param)        0.0961 M       10.40 μs   ±193.83%        8.94 μs       27.95 μs

Comparison: 
fun.(param)                                 8.42 M
apply(fun, param)                           8.27 M - 1.02x slower +0.00213 μs
apply_elixir_fun(Server, fun, param)       0.103 M - 82.08x slower +9.63 μs
apply_elixir_fun(Pool, fun, param)        0.0961 M - 87.54x slower +10.28 μs

Qqwy

Qqwy

TypeCheck Core Team

Version 0.3.0 has been released.
It now has been thoroughly battle-tested by using it from within ArraysRRBVector. :blush:

Usage has become slightly simpler and less error-prone; on the Rust side an enum is now returned with the various possible results of a function call:

pub enum ElixirFunCallResult {
    /// The happy path: The function completed successfully. In Elixir, this looks like `{:ok, value}`
    Success(StoredTerm),
    /// An exception was raised. In Elixir, this looks like `{:error, {:exception, some_exception}}`
    ExceptionRaised(StoredTerm),
    /// The code attempted to exit the process using a call to `exit(val)`. In Elixir, this looks like `{:error, {:exit, val}}`
    Exited(StoredTerm),
    /// A raw value was thrown using `throw(val)`. In Elixir, this looks like `{:error, {:thrown, val}}`
    ValueThrown(StoredTerm),
    /// The function took too long to complete. In Elixir, this looks like `{:error, :timeout}`
    TimedOut,
}

This way, we can be sure that all edge cases are considered when you use it in your code.

Where Next?

Popular in Libraries Top

deadtrickster
I’ve just released stable versions of my Prometheus Elixir libs: Elixir client [docs]; Ecto collector [docs]; Plugs instrumenter/Export...
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 49522 488
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 35421 110
New
arkgil
Hi all! I’m happy to announce that Telemetry v0.3.0 is out! This release marks the conversion from Elixir to Erlang so that all the libr...
New
woutdp
Hi! I wanted to introduce my latest project LiveSvelte. It allows you to render Svelte inside LiveView with end-to-end reactivity. It’s ...
New
jakub-zawislak
Hi everyone, I’m coming from the Symfony (PHP) framework. I like Phoenix, but it has a one thing that was build much better in the Symfo...
New
tmbb
I’ve published the first version of my Makeup library. It’s a syntax highlighter for Elixir in the spirit of Pygments, Currently it highl...
New
Qqwy
While not as prevalent as in imperative languages, arrays (collections with efficient random element access) are still very useful in Eli...
New
Azolo
Hey everyone, I just released WebSockex which is a Elixir WebSocket client. WebSockex strives to work as a OTP special process, be RFC6...
New
Hal9000
Here is my first stab at this. README pasted below. https://github.com/Hal9000/elixir_random Comments and critiques are welcome. Th...
New

Other popular topics Top

Qqwy
Update: How to use the Blogs & Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3268 119930 1237
New
peerreynders
Manning 2016 Halloween weekend sale via Deal of the Day Friday, October 28 - Half off all MEAPs - code WM102816LT Saturday, October 29 ...
326 29600 154
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
ycv005
I have followed this StackOverflow post to install the specific version of Erlang. And When I am running mix ecto.setup then getting fol...
New
vac
Hi, I'm quite new in Elixir and I'm trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and ...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? https://hexdocs.pm/ecto/Ecto.Repo.h...
New
AstonJ
by Lance Halvorsen Elixir and Phoenix are generating tremendous excitement as an unbeatable platform for building modern web application...
460 27162 124
New
Nvim
Elixir appears to be a superior language to Python. I don’t see any advantage of Python over Elixir. Are there any?
New

Sub Categories:

We're in Beta

About us Mission Statement