Performance & BEAM Internals
Also: :timer.tc/1, flame graphs via eflambe, :erlang.statistics/1.
43.1 How the BEAM runs your code
One scheduler per CPU core; each runs processes from a run queue.
Preemptive scheduling by reductions (~4000 function calls per slice) → fair latency.
Per-process heaps and per-process GC — no stop-the-world pauses for the whole system.
Binaries > 64 bytes are reference-counted and shared off-heap; small binaries are copied.
Messages are copied between processes (except refc binaries / literals).
JIT (BeamAsm, OTP 24+) compiles BEAM bytecode to native code at load time.
Dirty schedulers for long-running NIFs and I/O.
43.2 Performance tips
Use the right data structure: maps for lookup, tuples for fixed records, ETS for shared reads.
Avoid length/1, ++, and Enum.at/2 in loops (O(n)); prepend and reverse.
Build output with iodata, not string concatenation.
Watch out for large messages between processes (copied) — keep state local.
Beware of sub-binary leaks: a tiny slice can keep a huge binary alive — use :binary.copy/1.
Avoid GenServer bottlenecks: read directly from ETS, partition work.
Use Task.async_stream for parallel I/O-bound work.
Keep mailboxes small; selective receive on huge mailboxes is slow.
Prefer pattern matching in function heads over cond/if chains.
Measure before optimizing.
43.3 Benchmarking & profiling
Also: :timer.tc/1, flame graphs via eflambe, :erlang.statistics/1.
Benchee.run(%{
"Enum.map" => fn -> Enum.map(1..10_000, &(&1 * 2)) end,
"for" => fn -> for x <- 1..10_000, do: x * 2 end
}, time: 5, memory_time: 2)mix profile.eprof -e "MyApp.run()"
mix profile.fprof -e "MyApp.run()"
mix profile.tprof -e "MyApp.run()" # 1.17+ / OTP 2743.4 Memory & garbage collection
:erlang.garbage_collect(pid)
Process.flag(:fullsweep_after, 10)
Process.hibernate(M, :f, []) # compact a mostly idle process
:erlang.system_info(:process_count)
:erlang.system_info(:atom_count)