Quant Memo
Core

Flame Graphs and Hotspot Analysis

A way to visualize where a program actually spends its time, stacking sampled call stacks into a picture that turns 'which function is slow' from a guessing game into something you can read off a chart.

Prerequisites: Profiling with Hardware Performance Counters, Latency vs Throughput

You've been told a trading system's order-to-ack path is too slow, and the code has thousands of function calls across dozens of files. Reading the source and guessing which function is the bottleneck almost never works — intuition about "slow code" is wrong more often than it's right, and a change made on a hunch can waste a day and fix nothing. What you actually need is a measurement that shows, honestly, where the CPU time goes.

A profiler answers this by sampling: thousands of times per second, it interrupts the program and records the full call stack — which function is running, and which function called it, all the way up to main. Do this for a few seconds under realistic load and you get thousands of stack snapshots. A flame graph turns that pile of snapshots into a single picture: each unique stack becomes a column of stacked boxes, one box per function, with the box's width proportional to how often that function appeared anywhere in a sampled stack. Width is time; the y-axis is stack depth, not time — a flame graph is not a timeline, it's a merged, upside-down family tree of the sampled calls. Functions that took a long time in total show up as wide plateaus, whether they were called once and ran a long time, or called a million times cheaply each — that distinction is exactly what makes wide, flat boxes near the top so easy to spot as "hotspots": functions doing real work, not just orchestrating calls to other functions.

main parse_message update_book rebalance_tree (hotspot) tokenize
Sampled stacks stack into columns; box width is total time in that function across all samples, not call order. The wide red plateau (rebalance_tree) is the hotspot — it dominates the width at its level regardless of how many other functions sit above or below it.

Worked example: reading the widths

Suppose a profiler collects 10,000 samples over ten seconds of live order-book updates. update_book appears in 6,000 of them, and within those, rebalance_tree (a function it calls) appears in 5,400. That's 54% of all wall-clock time spent inside rebalance_tree specifically — a plateau taking up just over half the graph's width at that row. Meanwhile parse_message appears in 3,000 samples (30%) but is itself a thin box, because the time inside it is spread across many small child functions, none dominant. The flame graph makes the decision obvious without reading a line of code: optimizing rebalance_tree — say, switching it from a full rebuild to an incremental update — has roughly 18× more upside than shaving cycles off parse_message's tokenizer, because 54% of total time beats 30% split across a dozen small functions with no single wide plateau among them.

What this means in practice

Flame graphs are most useful for finding where to spend optimization effort at all — before reaching for cache-line tricks or SIMD, confirm the hot function is actually hot. They're read top-down for "what dominates" and bottom-up (an inverted, or "icicle," variant) for "who calls this expensive leaf function most." A profile taken under synthetic, idle-system load can mislead: a function that's cheap when the cache is warm and the CPU is unloaded can dominate under real production contention, so profiles should be captured under conditions as close to production as practical.

A flame graph merges thousands of sampled call stacks into stacked boxes where width equals total time spent in that function, letting you spot the true hotspot — the widest plateau — instead of guessing from source code which function is slow.

A common misreading is treating flame graph x-position as a timeline, left-to-right in call order. It isn't — the x-axis is alphabetically or arbitrarily sorted samples merged from many different points in time, and only width (not position) carries information. Comparing two boxes' horizontal position to infer "this ran before that" is a category error the graph doesn't support.

Related concepts

Practice in interviews

Further reading

  • Gregg, Systems Performance, ch. 6 (CPUs) and Appendix D (flame graphs)
ShareTwitterLinkedIn