Allocation Timeline vs Allocation Sampling Profiler
Chrome’s Memory panel offers two allocation tools — Allocations on timeline and Allocation sampling — and choosing the wrong one either drowns you in overhead or hides the detail you need. This comparison, part of Using Allocation Timelines to Track Object Creation in the Browser DevTools & Performance Profiling Workflows section, explains how each works and gives a decision rule you can apply in seconds.
| Symptom | Root Cause | Immediate Action | Measurable Impact |
|---|---|---|---|
| Timeline recording makes the app unusably slow | Instrumented tracking on an allocation-heavy flow | Switch to Allocation sampling for the first pass | Overhead falls from several-fold to a few percent |
| Sampling profile shows a hot function but you need the objects | Sampling aggregates stacks; no per-object records | Re-record just that flow with the timeline | Clickable surviving objects with retainers |
| Timeline shows blue bars everywhere | Recording covers idle timers and unrelated work | Record a shorter window around one action | Blue bars align with the action under test |
| Need evidence from a long session (minutes) | Timeline data grows with every tracked object | Use sampling for long sessions | Profiles stay small and stable |
| Need to know whether allocations survive | Default sampling reports only live objects at stop | Use the timeline’s blue/grey bars, or sampling with GC checkboxes | Clear survivors-vs-churn distinction |
Root Cause: Instrumentation Versus Statistics
The two tools are built on different V8 mechanisms. Allocations on timeline uses heap object tracking: V8 assigns every new object an ID and records when it was allocated, and DevTools periodically captures heap statistics so it can draw a bar for each interval. When you stop, DevTools computes which of the tracked objects are still alive. Blue portions of each bar are allocations that survived; grey portions were collected. You can select an interval and browse the actual surviving objects, open their Retainers, and — if you enabled allocation stack capture — see where each was created. The cost is proportional to the number of allocations, so busy pages slow down considerably while recording.
Allocation sampling uses V8’s sampling heap profiler. Instead of tracking every allocation, it samples one allocation roughly every 32 KB of allocated memory and records the call stack at that point. The result is a statistical profile of which functions allocate the most bytes, with negligible overhead, displayed in Heavy, Tree and Chart views. By default only samples of objects still alive at the end are reported; newer Chrome versions add checkboxes to include objects discarded by minor and major GC, turning it into a churn profiler. You cannot click an individual object, and small infrequent allocations may not be sampled at all.
So the timeline is precise and object-level but expensive and short; sampling is approximate and function-level but cheap and suitable for long sessions. They are complementary, and the fastest workflow usually uses both: sampling to find the hot function over a realistic session, then a short timeline recording around that function’s flow to inspect the objects. Where retention rather than allocation is the question, both are secondary to a heap snapshot comparison.
Step-by-Step Fix
- State the question. Decide whether you need to know which function allocates the most (ranking) or which specific objects survive and why (inspection). Verification: you can write the question in one sentence.
- For ranking, start with sampling. In DevTools → Memory, choose Allocation sampling, tick the “discarded by GC” boxes if chasing churn, record a realistic session of one to five minutes, and sort Heavy (Bottom Up) by Self Size. Verification: the top two or three functions account for most of the bytes.
- For inspection, record a short timeline. Choose Allocations on timeline, tick Record stack traces of allocations if you need creation sites, and record 10–30 seconds covering only the suspect action repeated two or three times. Verification: blue bars appear at each repetition.
- Use sampling output to target the timeline. Take the top function from step 2 and design the timeline recording in step 3 to exercise exactly the flow that calls it. Verification: in the timeline’s Allocation perspective, the same function appears near the top.
- Confirm survival. In the timeline, select the blue bar for one repetition and check whether the objects created there are still alive at the end. Verification: objects that should have been released show up as survivors — a leak — or the bars turn grey — churn only.
- Choose the fix type from the answer. Survivors mean a retention fix (release references); grey churn means an allocation-rate fix (reuse, pool, avoid copies). Verification: after the fix, re-run the same tool with the same settings and compare.
Command and Code Reference
Use case: run both profilers from a script for the same flow. Having both artefacts for one scenario makes the comparison concrete and gives reviewers something to open.
// both-profilers.mjs — node both-profilers.mjs
import puppeteer from 'puppeteer';
import { writeFileSync } from 'node:fs';
const browser = await puppeteer.launch();
const page = await browser.newPage();
const cdp = await page.createCDPSession();
await page.goto('http://localhost:5173/', { waitUntil: 'networkidle0' });
await cdp.send('HeapProfiler.enable');
const flow = async () => {
for (let i = 0; i < 3; i++) {
await page.click('#load-more');
await page.waitForNetworkIdle();
}
};
// 1) Sampling: cheap, function-level; keep GC'd objects to see churn too
await cdp.send('HeapProfiler.startSampling', {
samplingInterval: 32768,
includeObjectsCollectedByMajorGC: true,
includeObjectsCollectedByMinorGC: true,
});
await flow();
const { profile } = await cdp.send('HeapProfiler.stopSampling');
writeFileSync('flow.heapprofile', JSON.stringify(profile)); // Memory → Load
// 2) Timeline: precise, object-level; same flow, short window
const chunks = [];
cdp.on('HeapProfiler.addHeapSnapshotChunk', ({ chunk }) => chunks.push(chunk));
await cdp.send('HeapProfiler.startTrackingHeapObjects', { trackAllocations: true });
await flow();
await cdp.send('HeapProfiler.stopTrackingHeapObjects', { reportProgress: false });
writeFileSync('flow.heaptimeline', chunks.join(''));
await browser.close();
Use case: measure the overhead yourself. Timing the same flow with and without each profiler shows why sampling is the right first step on heavy pages.
// Run inside page.evaluate() or the Console around the same flow
async function timeFlow(run) {
const t0 = performance.now();
await run(); // the user flow under test
return Math.round(performance.now() - t0); // ms
}
// Typical result on an allocation-heavy list: 420 ms idle profiler,
// ~440 ms with sampling, ~1,900 ms with timeline + stack capture
Verification and Regression Prevention
You chose correctly if the tool answered the question in one recording: sampling produced a clear top allocator, or the timeline showed specific surviving objects with retainers you could act on. If a sampling profile is flat, extend the session or lower the sampling interval; if a timeline is too noisy, shorten the window and remove background timers. After fixing, re-run the same tool with the same settings so the comparison is meaningful.
For ongoing protection, automate the cheap tool and keep the expensive one manual. A sampling profile of a scripted flow can run on every pull request with little cost and fail when a function’s bytes exceed a budget; timeline recordings are better reserved for investigation because their overhead makes timings unreliable. The GC-oriented follow-up in reducing garbage churn found in allocation timelines covers what to change once either tool has found the allocator.
Edge Cases and Gotchas
Sampling interval versus rare, large allocations
Because sampling is proportional to bytes, a single 20 MB ArrayBuffer is almost certain to be sampled, while a million 16-byte objects are sampled many times too — but a function that allocates a few small objects rarely will be missed. If the question is “who allocated this rare object?”, sampling is the wrong tool; the timeline with stacks answers it directly.
External memory is not counted equally
Both tools focus on the JavaScript heap. Memory held outside it — ArrayBuffer backing stores, decoded images, canvas pixels — shows up as a small JavaScript wrapper with a large external cost that neither tool attributes precisely. For those, pair the profilers with process-level metrics and the techniques in ArrayBuffer and Blob memory outside the JS heap.
Workers and iframes are separate targets
Each worker and cross-origin iframe has its own heap. Select the right JavaScript VM instance at the bottom of the Memory panel’s profile-type list before recording, otherwise you profile the main page while the allocations happen elsewhere. The same applies to scripted CDP sessions, which attach to one target at a time.
Results depend on the build
Development builds of frameworks allocate far more than production builds — extra validation objects, dev-only warnings, component stacks. Profile a production build (with source maps) before drawing conclusions about allocation volume, and use development builds only when you need their readable names.
Frequently Asked Questions
Which one should I learn first?
Allocation sampling. It is cheap enough to run on any page for minutes, and its Heavy view immediately answers the most common question — which function allocates the most. Learn the timeline next, for the cases where you must inspect individual surviving objects.
Can the timeline tell me about garbage that was collected?
Yes, in aggregate: grey portions of the bars show how much of each interval’s allocation was collected before you stopped. It cannot show you those objects individually, because they no longer exist. For function-level attribution of collected garbage, use sampling with the “discarded by GC” options.
Do both tools work for Node.js?
Yes. Connecting Chrome DevTools to node --inspect exposes the same Memory panel profiles. Node also offers --heap-prof for sampling from startup without DevTools, which writes a .heapprofile file you can load into the Memory panel.
Related
- Using Allocation Timelines to Track Object Creation — the parent topic
- Using the Allocation Sampling Profiler in Chrome — a full walkthrough of the sampling tool
- Recording Allocation Stacks to Find the Allocating Line — getting the most out of the timeline
- Browser DevTools & Performance Profiling Workflows — the section overview