3. BEAM File
● A simple container with sections
○ Code
○ LitT, StrT (literals and strings)
○ Atom, AtU8
● Literals have dedicated location in memory
(constant pool)
● Access is O(1)
● Used literals are copied during code upgrade
“FOR1”, Length:32/big, “BEAM”
“Code”, Length:32/big,
Code/binary
“LitT”, Length:32/big,
Compressed/binary
…
4. Term
● Is a machine word, 64 (or 32) bit
● Has 2 bits reserved at all times, these bits
define the contained data type
Can be:
● An immediate value
● C pointer to a list cell
● C pointer to a boxed value
● A header, marks beginning of a “box”
a machine word
5. BEAM VM Structures are all terms
● Heap — array[] of Term
● Stack — array[] of Term, inside the young heap
● VM Registers — array[] of Term
Data types on heap
● Tuple — array[] of Term on heap
● List — array[2] of Term
● Binary & Bit String — array[] of words
● Float — one C double on heap (stored as 2 or 3 words)
6. Immediate value facts
● Always have size of 1 word
● Extra 2 or 4 bits define the type of data
Can be
● Small integers
● Local pids and ports
● Atoms
● NIL [] (empty list)
● Internal catch values
An immediate-2 value
An immediate-1 value
60 (28) bits for IMM1 value
64 (32) bits
58 (26) bits for IMM2 value
* VM NIL is not Elixir nil
7. List facts
● C-style single linked list
● A list cell is 2 words, containing the
head and the tail.
● Any list term is a pointer to a cell
● Last element may be a NIL []
● Can only iterate forward
● Cheap to prepend
[0 | []] 1 cell
[0 | [1 | []]] 2 cells
A list value, C pointer
1
Tail: [ ]
0
List value for the Tail, C pointer
* NIL in VM represents an empty list
[] and is not the same as Elixir nil
8. List tricks
● Store another value instead of a trailing NIL
○ Improper list
○ [X | Y] takes 2 words
which is smaller, than [X, Y | []] — 4 words,
also smaller, than {X, Y} — 3 words
● Reversing a list is cheap (copy)
○ Better than an inefficient algorithm which builds the result forward
● Reusing any tail of any list cell in multiple values is cheap.
This also means prepending any value.
● LC can be optimised if the result is not used (the unused
list will never be created)
X
Y
9. Lists: Please avoid
● Length operation is O(N), appending is O(N), finding Nth
element is O(N).
● – – operation is O(N*M), use ordsets or gb_sets for
the right arg
○ It is not too bad if the right argument is short.
○ Improved in OTP 22
● ++ operation is O(N) because: finding last element.
● lists:flatten, lists:reverse build a new list.
● When doing list-building, ensure that your code is
tail-recursive.
10. IO Lists
● Nested lists which contain other
lists or single characters or binaries
(also Elixir strings)
● Easy to “flatten” to a binary or a
string when is ready O(N); many
functions can consume from an
iolist without flattening
● Prepend and append are O(1)
● Fast and efficient
[
“Hello”,
“, ”,
[‘w’, “orl”],
[<<“d!”>>],
[]
]
11. IO Lists
● Prepending X to Y is O(1), but sometimes you need also to append:
○ [X | Y] — X is inserted before the first element of Y
○ [Y, X] — iolist is created, where X goes after Y — very cheap operation
● You don’t have to rebuild the large list to join multiple lists!
○ [X, Y, Z, T] where X, Y, Z and T are large lists — creates an iolist
○ Replaces lists:append
● Many functions in Erlang accept iolists as well as strings
○ File and socket functions, unicode functions, printing etc.
○ Types to look for: iolist() or iodata() (defined literally as iolist() | binary())
● Do more IO lists, it is good
12. a machine word
Term
● Is a machine word, 64 (or 32) bit
● Has 2 bits reserved, these bits define the
contained data type
Can be:
● An immediate value
● Can be a C pointer to a list cell
● Can be a C pointer to a boxed value
● Can be a header, marks beginning of a box
13. Boxed values
● Term value contains a pointer
● Binaries, floats, tuples, maps, local and
external refs, pids, and ports, big integers,
function closures, exports. Also temporary
data for internal functions
A boxed value, C pointer
14. Boxed values
● Term value contains a pointer
● Boxes always have 1 word header
● Binaries, floats, tuples, maps, local and
external refs, pids, and ports, big integers,
function closures, exports. Also temporary
data for internal functions
● Boxes always have 1 word header
○ Subtag (yellow bits), which defines what’s in the
box, and arity which defines size
A boxed value, C pointer
Header
array[] of Termarray[] of Termarray[] of word (term)
15. Tuple
● Tuple is a boxed value and exists on heap
{hello, “world”}
Tuple, C pointer
16. Tuple
● Tuple is a boxed value and exists on heap
● Tuple has 1 word header with tuple tag bits
and size (arity)
● Arity is limited to 26 bits (67M elements)
● Elements are a simple term array
● Changing a tuple element makes a full copy
except when the compiler can optimize it
Tuple, C pointer
Header
array[] of Termarray[] of Termarray[] of Term
17. Tuple tricks
● Random value lookup:
○ Accessing a random tuple element is O(1)
○ A literal tuple in your module is O(1)
● Setting multiple elements of a tuple will be optimized if:
○ Descending integer indexes are used
○ Tuple result is chained to the next setelement
○ No other function calls happen in between
● In other situations consider converting tuple to a list, map,
tree, or a more complex structure
18. Map facts
● Map is a boxed value and exists on heap
Map, C pointer
19. Map facts
● Map is a boxed value and exists on heap
○ Shorter than 32 keys: sorted list
○ HAMT (Hash Array Mapped Trie)
● Update is slow
● Lookup is faster than n-th list element
● Lookup is slower than indexing a tuple
HAMT
Map, C pointer
{Key, Value,
Key2, Value, ...}
Header
Header
20. Binary facts
● Binary is a boxed value and exists on
some heap
● A large binary is made of:
○ a heap bin on the binary heap
○ a refc bin on the process heap which points
to a refc bin
● A proc bin is a local small binary with a
2 word header and < 64b data
● A subbinary and match context are two
special cases
A process
PROC bin
REFC bin
Binary heap
HEAP bin
A process
REFC bin
SUB bin
21. Binaries: The good news
● Chain of binary append operations will be optimized if there
was ONLY ONE use of that binary throughout the operation.
● Unused variables in a binary match can be optimized away
○ Skipping/unused part of binary in all function clauses is globally
optimized away
f(<<_,X/binary>>) -> …
● How to see binary optimizations:
○ erlc +bin_opt_info MyModule.erl
○ export ERL_COMPILER_OPTIONS=bin_opt_info
22. Binaries: Please avoid
● Exposing large bin to multiple processes increases refcount and
holds the binary alive until the GC runs on all these processes!
● When growing a binary — a copy is created:
○ When the binary is sent as a message in the middle of manipulation
○ When the binary is inserted into ETS, sent to a port or to a NIF
○ When matching a binary (match context creates a pointer to the binary data)
23. External term format (ETF)
● Produced by erlang:term_to_binary call
● ETF is used when
○ Sending terms over network
○ Storing terms on disk: Mnesia, DETS, disk_log module
○ Database binary fields with Erlang term in them
● Atoms may be optimal in memory, but not in ETF
○ Not always true: ETF protocol is able to carry an atom table between connected nodes
○ Remote pids and remote ports contain hostname atom
○ For making the size smaller one can e.g. change atoms to small integers
24. Exports/Imports and Closures
● Export is a function reference:
○ Erlang: fun lists:foreach/1
○ Elixir: &Enum.each/2
● Represented internally as a {M, F, Arity} + extra fields
● A closure is same as an export + some captured values
● Sending function closures and exports over the network is not nice
○ The function code may be missing on the remote end
○ The remote code may be outdated
○ The remote code might be not what you think
25. Other
● Float takes (2 or 3 words on the heap) + 1 for the term itself — expensive.
○ Consider N/M rational fractions
○ Consider fixed point integers
● Small integer 1 word (28 or 60 bits w/sign) automatically becomes a BIG
integer on overflow (3x memory usage)
26. Binary
Heap
Data is copied, when it
● leaves the process as a message to
another process or port
● is used as arguments when spawning a
process
● is exchanged with ETS
Exception:
● Large binaries > 64 bytes are reference
counted and only references are copied
Process
Value
ETS
Ports
Other
Processes
Value
64+ bytes
Reference Value
27. Profile and measure
● Knowing the theory helps, but one must also know their data!
● Do not guess
● Print (see) and inspect your data structures
● Profile and measure your memory
○ erlang:system_info
○ erlang:process_info
○ erts_debug:size/1
○ erts_debug:flat_size/1
28. Performance: Optimize First
● Disk storage access
● Database access and slow queries
● Remote network access
…..
● Inefficient and slow algorithms & data structures
29. More information about internal structure for
BEAM memory can be found at
http://beam-wisdoms.clau.se
also
happi/theBeamBook
Dmytro Lytovchenko
Erlang Solutions, Sweden
@kvakvs