DEV Community

Discussion on: How does your language handle memory?

Collapse
 
rhnonose profile image
Rodrigo Nonose

Elixir is compiled to run on the Erlang VM (BEAM) so this applies to both Elixir, Erlang and other languages compiled to BEAM.
On BEAM, everything runs on a process (not to be mixed with OS processes neither OS threads, it's a user level process).
The processes have isolated memory.
The following wall of text is, in my opinion, the "architectural result" of the runtime based on this.

Only the process itself can access its memory and communication between processes are made by asynchronous messages (hard-copied), placed in a process mailbox that have to be explicitly accessed by the receiving process.
There's a bunch of trade-offs on the choice of this model.

  • It introduces asynchronicity when sharing data.
  • Since messages are hard-copied, it consumes significantly more memory. There's some optimizations for that, like big strings belonging in a shared table being "read-only".
  • Inside the process, data is immutable. Meaning, if I take a piece of memory (any type) and modify it, a new one will be created. This also why (I believe) there's no array type, only linked lists (and modified linked lists doesn't always create a full copy, just a "diff").
  • Every process can be executed in parallel and the VM does it by default: the VM runs in a single OS process, which spawns (by default) a thread per physical core of the machine that serves as schedulers. The schedulers then have separate independent queues that runs the processes. Differently from most runtimes, the scheduling is preemptive rather than cooperative, meaning the scheduler is the one that decides how long the process is going to run.
  • Processes can't have (or doesn't have, I still can't properly find the details on this) "goto loops": loops are mandatorily recursive function calls (like a real functional language).
  • Every process can have its turn: since every process is executed a little bit, long-running processes don't block short-running ones, making the runtime highly available.
  • Garbage collection is pretty straight-forward, because it doesn't have to check pointers and can safely free memory if the process finished executing. Also processes that are sleeping (usually waiting for messages) doesn't consume CPU at all because it gets woken up by the scheduler.
  • Allows process supervision. If a process dies because of an unexpected error, the supervisor restarts it. It can restart other processes that depends on the process that died as well, enabling graceful restarts. It enables the erlang philosophy of "let it crash" (most of the bugs are solved by restarting stuff).
Collapse
 
rhymes profile image
rhymes

Love this, thanks :-)