Observation Guide
Seeing shapes and ICs directly
The measurements in this module are black-box. To see the mechanism, use V8 flags via node
(same engine, no browser needed):
node --allow-natives-syntax -e '
const a = { x: 1, y: 2 };
const b = { y: 2, x: 1 };
console.log(%HaveSameMap(a, b)); // false — different transition chains
const c = { x: 1, y: 2, tmp: 0 };
delete c.tmp;
console.log(%HasFastProperties(c)); // false — dictionary mode
'
| Flag | Shows |
|---|---|
--allow-natives-syntax | enables %HaveSameMap, %HasFastProperties, %DebugPrint |
--trace-ic | every IC state transition (very verbose) |
--trace-deopt | deoptimisations and their reasons |
--trace-opt | which functions get optimised, and when |
--print-opt-code | generated machine code (rarely useful in practice) |
%DebugPrint(obj) prints the hidden class and property layout. This is the fastest way to confirm
a shape hypothesis, and far more reliable than inferring it from timings.
These are debugging tools, not decision tools. Nothing here tells you whether an optimisation is worth doing — only what the engine is doing. The perspective table answers "worth it".
Chrome DevTools
The Performance panel's bottom-up view attributes time to functions, not to shapes. There is no "megamorphic access" marker, and this is deliberate: at 2.9 ns, it would be noise in any real profile.
What you can see, and what actually matters:
| Signal | Where | Meaning |
|---|---|---|
| Large "Scripting" blocks | flame chart | worth investigating — but usually algorithmic, not shapes |
| Forced reflow warnings | red triangle | 4,200 ns each. Fix these first |
Long JSON.parse / Recalculate Style | flame chart | ~400,000 ns and up. Fix these before anything |
| GC blocks | Memory track | see fe-02; a leak presents here |
The ordering of that table is the observation skill. Engineers reach for JIT explanations when the flame chart shows a big scripting block, and the cause is nearly always an algorithm, a waterfall, or layout thrashing.
Healthy vs unhealthy
Healthy:
- Objects for one logical entity constructed with the same properties in the same order
- No
deleteon objects in hot paths - Numeric arrays that stay numeric
Mapused where keys are dynamic- Nobody on the team can tell you the IC state of anything, because nobody needed to look
Unhealthy — but check the magnitude before acting:
- Object literals built conditionally so property sets vary per branch
deletein a per-item loop- Arrays initialised as
new Array(n)then filled sparsely - A "fast path" object mutated into a different shape by an error handler
Unhealthy — act immediately, unrelated to this module:
- Forced reflow warnings in a render path
- Parsing large JSON on the main thread
- A request waterfall
What these measurements do not tell you
- One engine, one version. V8 149. JavaScriptCore and SpiderMonkey differ in IC design and element-kind handling; no result here transfers without re-measurement.
- Micro-benchmarks are not applications. One shape through one site, versus your component receiving props from twelve call sites. Shape results routinely fail to reproduce in the app.
- Timer resolution is ~0.1 ms. Anything below ~5 ms per rep is quantisation, not measurement.
- No allocation or GC pressure is measured here. Shape churn also affects memory (fe-02), and this module measures only speed.
- The
PACKED_DOUBLEresult is unexplained. It reproduced, one hypothesis was tested and rejected, and it was not pursued because it is ~0.6 ns/element. That is a documented limit, not a finding.