Elixir on Erlang VM demystified

How does Elixir work under the hood?

BEAM

BEAM stands for Bogdan/Björn’s Erlang Abstract Machine, which is the virtual machine at the core of the Erlang Open Telecom Platform (OTP).

OTP

OTP, in turn, is a set of Erlang libraries, which consists of the Erlang Runtime System (ERTS), a number of ready-to-use components mainly written in Erlang, and a set of design principles for Erlang programs.

Finally, OTP provides modules and behaviours that represent standard implementations of common practices like process supervision, message passing, spawning tasks, etc. In order to install and use Erlang on your machine, you will install and build an OTP distribution.

Runtime

BEAM is also a part of ERTS (it’s included there) that compiles both Erlang and Elixir code into bytecode (saved in module_name.beam files). This means that Elixir code can be called from Erlang and vice versa, without the need to write any bindings.

The Elixir runtime is a BEAM instance. Once compiling is done, the OS process starts and Erlang takes the entire control. Everything runs inside this process and has access to the code because the VM tracks all modules in memory.

Scheduler

BEAM instance starts in a single OS process (you can look it up by its name beam ). For every available CPU, it creates a thread. This makes Erlang systems scalable because they can take advantage of all available cores.

As a part of the ERTS, the BEAM is responsible for scheduling Erlang processes. A single thread runs exactly one scheduler which is, among the others, responsible for populating Erlang process queue. The scheduler also pulls Erlang processes from the queue and allocates a time slot to execute each of them.

Processes

You may have already noticed Elixir doesn’t have threads. It has processes instead. Elixir (actually Erlang) processes, however, are not the same as OS processes. Typical Erlang systems run millions of them.

They have access only to their memory, they don’t share this memory between one another, nor is there a global memory that can be shared between them. And finally, since they share no memory, a crash of one process doesn’t cause a crash of another one.

The only coordination that may happen is via messages — they can send a copy of their data only. To receive messages, each process has a local message queue. Sending a message results in copying the message into the receiver heap and storing the message reference in the receiver message queue. While waiting for messages to be received a process is swapped out and is added to the scheduler queue only when a new message is received or a time-out occurs.

Concurrency

BEAM is responsible for scheduling Erlang processes. This is where concurrency magic happens. If a process exceeds its execution time slot, the Erlang VM pauses the process, puts it back on the queue and moves on to the next item on the list — the entire mechanism is called “preemption”. This way, BEAM executes Erlang processes concurrently by quickly switching back and forth between processes.

Erlang's processes are dynamically spawned and killed during the execution. Each process has its heap and stack area. For concurrency purposes, the Erlang BEAM provides suspension and scheduling mechanisms. A suspending process stores its current state in its suspension record and is added to a scheduler queue. To guarantee fair scheduling, a process is suspended after a fixed number of reductions and then the first process from the queue is resumed.

As you know, BEAM uses one OS thread per core and runs a scheduler on each of these threads. In the concurrent but one-core world, The Erlang VM ran one thread with one scheduler on the one available core. However, in a multi-core environment, there are as many schedulers as available CPUs there.

We can see that in action by starting an iex shell on our machines:

$ iex

Erlang/OTP 22 [erts-10.6.1] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe] [dtrace] Interactive Elixir (1.9.4) - press Ctrl+C to exit (type h() ENTER for help)

iex(1)>

This portion of the prompt [smp:4:4] indicates that my machine has four available cores, with four schedulers available — one per a thread on each core.

The processes run concurrently, thus they may act in parallel considering more than two cores are available on the given machine.

Subscribe to get the latest content immediately

https://tinyletter.com/KamilLelonek

Summary

I hope this article gave you a brief overview of Elixir, Erlang and their components including BEAM. I believe it’s important for programmers to understand how everything they do works, even if they can’t see or don’t want to see it. I truly enjoy being in charge of everything my code does. I also love to feel that it does exactly what I want it to do. It’s important to understand what is happening under the hood to fix all bugs with a full understanding and call it a day. Do you feel the same?