Loading...
Loading...
V8 is a high-performance JavaScript engine by Google, used in Chrome and Node.js. Understanding its architecture is critical for writing optimized code.
function add(a, b) {
return a + b;
}
AST (Abstract Syntax Tree):
{
"type": "FunctionDeclaration",
"id": { "name": "add" },
"params": [
{ "name": "a" },
{ "name": "b" }
],
"body": {
"type": "ReturnStatement",
"argument": {
"type": "BinaryExpression",
"operator": "+",
"left": { "name": "a" },
"right": { "name": "b" }
}
}
}
Ignition converts AST to bytecode and starts execution:
// Bytecode for add(a, b)
Ldar a0 // Load argument 0 (a) into accumulator
Add a1, [0] // Add argument 1 (b)
Return // Return result
Why bytecode?
Sparkplug (added in V8 9.1) compiles frequently called bytecode to unoptimized machine code:
Bytecode → Machine Code (1:1 mapping)
Advantages:
TurboFan creates highly optimized machine code based on feedback:
function add(a, b) {
return a + b;
}
// After 1000+ calls with numbers
add(1, 2); // V8 notices: always numbers!
add(5, 10);
add(100, 200);
// TurboFan optimizes: assuming numbers
// Generates machine code for numbers directly
TurboFan Optimizations:
function calculate(x) {
return x * 2;
}
// Call 1-100: Ignition (bytecode)
for (let i = 0; i < 100; i++) calculate(i);
// Call 100+: Sparkplug (baseline)
// V8: "This function is hot, compile to baseline"
// Call 1000+: TurboFan (optimized)
// V8: "Always numbers! Optimize for numbers"
function calculate(x) {
return x * 2;
}
// V8 optimized for numbers
for (let i = 0; i < 10000; i++) {
calculate(i); // Numbers - optimized code
}
// Unexpected type!
calculate("hello"); // String - DEOPT!
// V8 rolls back to bytecode and collects feedback again
Deopt Cost:
V8 optimizes property access through Hidden Classes:
class Point {
constructor(x, y) {
this.x = x; // Hidden Class C0 → C1
this.y = y; // Hidden Class C1 → C2
}
}
const p1 = new Point(1, 2);
const p2 = new Point(3, 4);
// p1 and p2 have same Hidden Class C2
Hidden Class stores:
// Bad: different Hidden Classes
const p1 = { x: 1, y: 2 };
const p2 = { y: 2, x: 1 }; // Different order!
// Bad: dynamic properties
const p3 = { x: 1, y: 2 };
p3.z = 3; // New Hidden Class!
// Good: same structure
const p4 = { x: 1, y: 2 };
const p5 = { x: 3, y: 4 }; // Same Hidden Class
Inline Cache speeds up property access by caching their location:
function getX(point) {
return point.x;
}
// First call
getX({ x: 1, y: 2 });
// V8: "x is at offset 0 for Hidden Class C2"
// Subsequent calls
getX({ x: 3, y: 4 });
// V8: "Same Hidden Class! Use cache - offset 0"
IC Types:
// Always one Hidden Class
getX({ x: 1, y: 2 });
getX({ x: 3, y: 4 });
getX({ x: 1, y: 2 });
getX({ x: 1, y: 2, z: 3 }); // Different Hidden Class
// Bad: too many different structures
for (let i = 0; i < 10; i++) {
getX({ x: 1, [i]: i }); // New Hidden Class every time!
}
Idea: Most objects die young.
Scavenge GC (Young Generation):
// Create temporary objects
function process() {
const temp = { data: new Array(1000) }; // Dies immediately
return temp.data.length;
}
// temp destroyed by Scavenge GC (~1-2ms)
Mark-Sweep-Compact (Old Generation):
// Long-lived objects
const cache = new Map(); // Survives Scavenge → Old Gen
cache.set('key', largeData);
// Removed by Major GC (~50-100ms)
Don't change function argument types. Use TypeScript for type control.
Initialize all properties in constructor. Don't add properties dynamically.
Functions should work with objects of same structure (Hidden Class).
delete obj.prop creates new Hidden Class. Use obj.prop = undefined.
// Bad: different types
function add(a, b) {
return a + b;
}
add(1, 2); // Numbers
add("a", "b"); // Strings - DEOPT!
// Good: one type
function addNumbers(a, b) {
return a + b;
}
function addStrings(a, b) {
return a + b;
}
// Bad: dynamic properties
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
const p = new Point(1, 2);
p.z = 3; // New Hidden Class!
// Good: all properties in constructor
class Point3D {
constructor(x, y, z = 0) {
this.x = x;
this.y = y;
this.z = z; // Always same structure
}
}
// Check function optimization
%OptimizeFunctionOnNextCall(myFunction); // --allow-natives-syntax
myFunction();
%GetOptimizationStatus(myFunction);
# Run with V8 flags
node --trace-opt --trace-deopt app.js
# Output:
# [optimizing: myFunction / 0x...]
# [bailout: myFunction - type mismatch]
Summary:
V8 uses multi-tier compilation (Ignition → Sparkplug → TurboFan) for balance between startup speed and performance. Understanding Hidden Classes, Inline Caching, and deoptimization conditions helps write code that V8 can efficiently optimize.