Part 8 · 1 chapters · ~8 min

Performance

The BEAM's latency-first design, the BeamAsm JIT, finding bottleneck processes with recon and observer, message copying and reference-counted binaries, ETS and persistent_term for shared reads, Rustler NIFs and dirty schedulers, Nx for numerical work, Benchee, profiling with :eprof and :fprof, and Ecto query performance.

9

Find the hot mailbox

code
:recon.proc_count(:message_queue_len, 5)      # processes with the longest mailboxes
:recon.proc_count(:reductions, 5)             # who is using CPU
Benchee.run(%{"Enum" => fn -> Enum.sum(list) end, "reduce" => fn -> Enum.reduce(list, 0, &+/2) end})

# shared, rarely changing config: persistent_term reads are nearly free (writes are expensive)
:persistent_term.put({:fx, "USD-NGN"}, 1_532_00)

# a Rust NIF via Rustler for CPU-heavy work
defmodule Hash do
  use Rustler, otp_app: :ledger, crate: "hash"
  def blake3(_bin), do: :erlang.nif_error(:nif_not_loaded)
end
ELIXIR PERFORMANCE
where time goes on the BEAM
throughput vs latencyBEAM optimises for consistentlatency under load, not raw CPUspeed.CPU-heavy workSlower than Go or Rust for numbercrunching; use NIFs (Rustler) orNx.bottleneck processesOne GenServer handles one messageat a time: find hot mailboxes.copyingLarge messages are copied;binaries over 64 bytes are sharedby reference.the JITBeamAsm JIT since OTP 24 compilesto native code on load.toolsBenchee, :fprof, :eprof, recon,observer, telemetry.
swipe the figure sideways, or tap expand for full screen
1/4
the trade-off
The BEAM trades peak single-thread speed for preemption, isolation and predictable latency: tail latency stays flat under overload when other runtimes queue up.
latency over raw speedflat tails under load