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
'
FlagShows
--allow-natives-syntaxenables %HaveSameMap, %HasFastProperties, %DebugPrint
--trace-icevery IC state transition (very verbose)
--trace-deoptdeoptimisations and their reasons
--trace-optwhich functions get optimised, and when
--print-opt-codegenerated 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:

SignalWhereMeaning
Large "Scripting" blocksflame chartworth investigating — but usually algorithmic, not shapes
Forced reflow warningsred triangle4,200 ns each. Fix these first
Long JSON.parse / Recalculate Styleflame chart~400,000 ns and up. Fix these before anything
GC blocksMemory tracksee 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 delete on objects in hot paths
  • Numeric arrays that stay numeric
  • Map used 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
  • delete in 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_DOUBLE result 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.