F4.10 · part 3 of 3 · choosing a lookup
Profiling & complexity
Every active session has to be found on every request. Keep sessions in a list and a lookup scans
it — O(n), growing with the user base. Keep them in the branded-CHAMP store of
F4.09 and a lookup descends a few trie levels — O(log₃₂ n), effectively
flat. The right structure is not a matter of taste here; you can read the difference straight from the code and
watch it widen as the Portal grows.
The same lookup, the two structures, at three user-base sizes. Pick a size and compare the work each one does.
Read the cost from the shape
A list scan touches up to one element per entry; a CHAMP lookup touches one node per five bits of the hash, so its depth grows with the logarithm of the count, base thirty-two. Pick a size to see both as operation counts.
Active sessions · select one
list scan: 1,000 ops
map lookup: 2 hops
The list and the map agree at small sizes and diverge without bound. At ten million sessions the scan is millions of comparisons and the map is five hops — the reason the Portal’s store is a CHAMP.
Advanced: measure, but reason first
Reasoning about complexity tells you how a cost scales; measuring with
:timer.tc/1 tells you what it is at one size on one machine. You want both, in that
order. Big-O picks the structure — a lookup that must stay cheap as the user base grows cannot be a list
— and a measurement confirms the constant factors are sane once it is built. Reaching for the profiler
first tends to optimise a fast operation while an O(n) one waits to dominate at scale.
Two honest caveats. O(log₃₂ n) is not literally
O(1): it is at most about six hops for any count up to a billion, so it is flat in
practice but still grows. And constant factors cut the other way at small sizes — a list of ten entries
is contiguous and needs no hashing, so it beats a trie there. The point is not that maps are always faster;
it is that the Portal’s collections grow, and a structure whose lookup is logarithmic stays usable where
a linear one does not.
# reason: which scales? then measure to confirm the constant factor {t_list, _} = :timer.tc(fn -> List.keyfind(sessions, sid, 0) end) # O(n) {t_map, _} = :timer.tc(fn -> Champ.get(store, sid) end) # O(log₃₂ n) # at 10_000_000 sessions: ~10_000_000 comparisons vs ~5 node hops # the list keeps growing; the map barely moves — so the store is a CHAMP
a structure choice
List or map for the session store.
a complexity
O(n) that grows with users, or O(log₃₂ n) that stays flat.
That completes F4.10: the request validates with a with chain, reports
stream lazily, and lookups ride a structure chosen for how it scales. Next module: F4.11 —
Dynamic programming in Elixir.
References
Primary sources for this lesson, and where it connects in the course.
Sources
- Erlang — Efficiency Guide: list handling — the cost of scanning a list.
- Erlang —
:timer.tc/1— measuring a call's wall-clock time. - Big O notation — Wikipedia — reasoning about how cost scales.
Related in this course
- F4.04 · Maps, sets & hashing — the map whose lookup is logarithmic.
- F4.09 · Branded CHAMP maps & GenServer — the store this lookup runs against.
- F4.10 · Practical recipes in Elixir — the module hub.
- F4 · Algorithms & Data Structures