Skip to content
Lesson 8 of 8

Step 1 of 7 · Reading · ~4 min

Learn

Numbers and Performance

Performance and V8 Optimization

V8, the engine in Node and Chrome, runs JavaScript fast by guessing: it watches which kinds of values your code really handles and compiles machine code for those. By the end of this lesson you can predict which of your patterns keep those guesses valid, read the three inline-cache states, and measure instead of folklore.

From bytecode to machine code

Code starts in an interpreter that also records what it sees: which types flowed through each operation, which object layouts each property access met. When a function runs hot, an optimizing compiler (TurboFan) builds machine code that assumes those observations still hold, guarded by cheap checks. When a check fails, the engine throws the optimized code away and falls back to the interpreter: a deoptimization.

Hidden classes (shapes)

An object is not a hash table. V8 gives each object a hidden class (also called shape or map) listing its property names, their order, and where each value sits in memory. Objects with the same hidden class let obj.x compile to one memory read at a fixed offset. Adding a property moves the object to a new hidden class along a transition, so the order and the construction path matter. Checked with node --allow-natives-syntax and %HaveSameMap(a, b) (a debugging syntax, not for real code), on a recent Node:

Pair of objectsSame hidden class?
{x:1, y:2} and {x:5, y:6} from two separate literalsyes
{x:1, y:2} and {y:2, x:1}no (different order)
two objects returned by one factory functionyes
new P(1,2) and new P(1,2,3) where the constructor adds z only when givenno
a literal {x:1,y:2} and {} followed by o.x=1; o.y=2no (different path)
{x:1,y:2,z:3} after delete o.xdictionary mode: slow hash-table form

The practical rule: create objects of one kind through one constructor, class, or factory that always assigns the same properties in the same order, using undefined or null for "absent", and avoid delete on hot objects.

Inline caches: monomorphic, polymorphic, megamorphic

Every property-access site (a.x written at one place in your source) carries an inline cache remembering the hidden classes it has met. Its state is a count of distinct hidden classes seen at that site:

  • monomorphic: one. The compiled code is a single check plus a load. Fastest.
  • polymorphic: two to four. A short chain of checks. Slower.
  • megamorphic: five or more. The site gives up and does a generic lookup through a shared cache. Slowest, and it stays that way.

The cache belongs to the site, not to the program. A utility like function getId(o) { return o.id; } that is called with many unrelated object layouts is one megamorphic site for everything that calls it; the same call written inline at each place would be several monomorphic ones.

Other things that cost you a deopt

  • Changing the type in a hot spot: a function that always saw integers and then receives a string or undefined.
  • Array element kinds only widen (all-integer, then doubles, then anything), and holes (new Array(1000), delete arr[i], writing far past the end) make the array slower for good. Fill arrays densely with push.

Measure first

Real bottlenecks are rarely where intuition says. Tools: node --cpu-prof app.js writes a profile you can open in Chrome DevTools; node --prof plus --prof-process is the older route; --trace-deopt prints each deoptimization with its reason. A micro-benchmark must warm up first, run long enough to average out, and use its result, or the compiler may delete the loop as dead code. Time with process.hrtime.bigint().

String building is a good example of folklore: s += piece in a loop looks quadratic, but V8 stores the result as a lazy "rope" and flattens it once when you read it, so it is usually fine. parts.join('') is a safe habit, not a proven necessity.

Common mistakes

  • Optimizing without a profile, then keeping the change because it "felt" faster.
  • Sorting an object's keys, or creating its properties in a different order on different code paths, and thereby multiplying hidden classes.

Your exercise

You will model the inline cache bookkeeping itself: shapes, the three states, and one cache per site. The usual slip is treating {x, y} and {y, x} as the same shape because they hold the same keys.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…