Running C code in Elixir/Erlang: Ports or NIFs?
Asked Answered
H

1

11

I've found that Elixir programs can run C code either via NIFs (native implemented functions) or via OS-level ports. Having read those and similar links, I'm not a hundred percent clear on when to use one or the other method (or something else entirely?), and feel it would be good to have a direct comparison available, for myself and other novices. Can anyone provide?

Halfback answered 4/2, 2017 at 2:42 Comment(2)
spin.atomicobject.com/2015/03/16/…Hassanhassell
@Hassanhassell That list is missing at least one point I've seen elsewhere: that NIFs prevent the BEAM from multitasking properly because it can't switch in the middle of executing one. I don't know if it might be missing other things as well.Halfback
J
25

What are ports?

Ports are basically separate programs which are run separately from the Erlang VM. The Erlang VM communicates with the running port over standard input/output, and the resulting port lives behind an Erlang process that owns it and can facilitate communication between the port and the rest of your Erlang or Elixir application. Ports are "safe" in the sense that if the port crashes, it doesn't bring down the whole Erlang VM.

Porcelain might be of interest as a possible improvement and expansion over what's already provided in the Port module. System.cmd/3 also uses ports in its underlying implementation.

What are NIFs?

Native inline functions or "NIFs" are functions defined in what are essentially shared libraries / DLLs loaded by the Erlang VM and written using some language which exposes a C-compatible ABI. NIFs are more efficient than ports (since they don't have to communicate over STDIN/STDOUT) and are simpler in many respects (since you don't have to deal with encoding and decoding data between your Elixir and non-Elixir codebases), but they're also much less safe; a NIF can crash the Erlang VM, and a long-running NIF can potentially lock up the Erlang VM (since the scheduler can't reason about native code).

What are port drivers?

Port drivers are kind of an in-between approach to integrating external code with an Erlang or Elixir codebase. Like NIFs, they're loaded into the Erlang VM, and a port driver can therefore crash or hang the whole VM. Like ports, they behave similarly to Erlang processes.

When should I use a port?

  • You want your external code to behave like an ordinary Erlang process (at least enough for such a process to wrap it and send/receive messages on behalf of your external code)
  • You want the Erlang VM to be able to survive your external code crashing
  • You want to implement a long-running task in your external code
  • You want to write your external code in a language that does not support C-compatible FFI (or otherwise don't want to deal with your language's FFI facilities)

When should I use a NIF?

  • You want your external code to behave like a collection of ordinary Erlang functions (particularly if you want to define an Erlang/Elixir module that exports functions implemented in native-compiled code)
  • You want to avoid any potential performance hits / overhead from communicating via standard input/output and/or you want to avoid having to translate between Erlang terms and something your external code understands
  • You are reasonably confident that the things your external code is doing are neither long-running nor likely to crash (including, in the latter case, if you're writing your NIFs in something like Rust; see also: Rustler), or...
  • You are reasonably confident that crashing or hanging the Erlang VM is acceptable for your use case (e.g. your code is both distributed and able to survive the sudden loss of an Erlang node, or you're writing a desktop application and an application-wide crash is not a big deal aside from being an inconvenience to users)

When should I use port drivers?

  • You want your external code to behave like an Erlang process
  • You want to avoid the overhead and/or complexity of communicating over standard input/output
  • You are reasonably confident that your port driver won't crash or hang the Erlang VM, or...
  • You are reasonably confident that a crash or hang of the Erlang VM is not a critical issue

What do you recommend?

There are two aspects to weigh here:

  • Process-like v. module-like
  • Safe v. efficient

If you want maximum safety behind a process-like interface, go with a port.

If you want maximum safety behind a module-like interface, go with a module with functions that either wrap System.cmd/3 or directly use a port to communicate with your external code

If you want better efficiency behind a process-like interface, go with a port driver.

If you want better efficiency behind a module-like interface, go with NIFs.

Jolt answered 4/2, 2017 at 22:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.