Docs
24.5-Performance-Optimization-Internals
22.5 Performance Optimization Internals
Overview
Understanding the internal mechanisms that affect JavaScript performance allows you to write faster code. This section covers optimization triggers, deoptimization patterns, and how to work withβnot againstβthe JavaScript engine.
Learning Objectives
By the end of this section, you will:
- β’Understand optimization and deoptimization triggers
- β’Identify common performance anti-patterns
- β’Learn about hot and cold code paths
- β’Master techniques for engine-friendly code
- β’Use profiling tools to identify bottlenecks
1. Optimization Tiers
V8's Tiered Compilation
Cold Code β Ignition (Interpreter)
β (becomes warm)
Warm Code β Sparkplug (Baseline Compiler)
β (becomes hot)
Hot Code β TurboFan (Optimizing Compiler)
How Code Becomes Hot
// Code is considered "hot" based on:
// 1. Execution count
// 2. Time spent in function
// 3. Loop iteration count
function hotFunction() {
// Called thousands of times
// Will be optimized by TurboFan
}
for (let i = 0; i < 10000; i++) {
hotFunction(); // Function becomes hot
}
2. Optimization Triggers
Type Consistency
// β
Good: Consistent types enable optimization
function add(a, b) {
return a + b;
}
// All calls with numbers - engine can optimize
add(1, 2);
add(3, 4);
add(5, 6);
// β Bad: Mixed types prevent optimization
add('hello', 'world'); // Now function handles strings too
add([1], [2]); // And arrays!
Predictable Control Flow
// β
Predictable branches can be optimized
function process(type) {
if (type === 'A') {
return handleA();
} else if (type === 'B') {
return handleB();
}
}
// Engine can predict: if type is usually 'A',
// it can optimize for that path
Inline Caching Hits
// β
Same object shape = IC hits
const points = [
{ x: 1, y: 2 },
{ x: 3, y: 4 },
{ x: 5, y: 6 },
];
// All objects have same shape - very fast
points.forEach((p) => p.x + p.y);
3. Deoptimization Triggers
What is Deoptimization?
When assumptions made during optimization are violated, the engine must "bail out" to slower, unoptimized code.
Common Deoptimization Causes
1. Type Changes
function calculate(value) {
return value * 2;
}
// First 1000 calls with numbers - optimized for numbers
for (let i = 0; i < 1000; i++) {
calculate(i);
}
// Suddenly a string! β DEOPT
calculate('oops'); // Forces deoptimization
2. Hidden Class Changes
function Point(x, y) {
this.x = x;
this.y = y;
}
const p1 = new Point(1, 2);
const p2 = new Point(3, 4);
// All points have same hidden class - good!
// But then:
p1.z = 5; // p1 has different hidden class now!
// β Polymorphic, then megamorphic if more variations
3. Arguments Object Issues
// β Using arguments in certain ways causes deopt
function badArgs() {
arguments[0] = 10; // Modifying arguments β deopt
return arguments;
}
// β
Better: Use rest parameters
function goodArgs(...args) {
return args;
}
4. Try-Catch in Hot Paths
// β try-catch can prevent optimization
function hotPath() {
try {
// Hot code here - may not optimize well
return compute();
} catch (e) {
return null;
}
}
// β
Better: Isolate try-catch
function safeCompute() {
try {
return compute();
} catch (e) {
return null;
}
}
function hotPath() {
// Hot code without try-catch
return safeCompute();
}
4. Optimization Killers
Things That Prevent Optimization
1. eval() and with
// β eval prevents many optimizations
function badEval(code) {
eval(code); // Engine can't know what this does
}
// β with prevents optimizations
function badWith(obj) {
with (obj) {
// Scope is unpredictable
return x;
}
}
2. Deleting Properties
// β delete changes object shape
const obj = { a: 1, b: 2 };
delete obj.a; // Shape change β deopt
// β
Better: Set to undefined if you must
const obj2 = { a: 1, b: 2 };
obj2.a = undefined; // Same shape
3. Leaking arguments
// β Leaking arguments prevents optimization
function leakArgs() {
return arguments; // Arguments escapes function
}
function storeArgs() {
window.args = arguments; // Leaked!
}
// β
Convert to array
function safeArgs() {
return [...arguments];
}
4. Polymorphic Operations
// β Operating on many different types
function process(input) {
return input.toString(); // Called on strings, numbers, objects, arrays...
}
// β
Better: Type-specific functions
function processString(str) {
return str;
}
function processNumber(num) {
return String(num);
}
function processObject(obj) {
return JSON.stringify(obj);
}
5. Hot and Cold Code Separation
Principle
Keep hot (frequently executed) code paths clean and simple. Move cold (rarely executed, error handling) code to separate functions.
// β Error handling in hot path
function processItems(items) {
for (const item of items) {
try {
// Hot code
const result = transform(item);
if (!result) {
console.log('Transform failed for:', item);
logError(new Error('Transform failed'));
notifyAdmin();
item.failed = true;
}
} catch (e) {
handleError(e, item);
}
}
}
// β
Separated concerns
function processItems(items) {
for (const item of items) {
const result = transform(item); // Hot path
if (!result) handleFailure(item); // Cold, moved out
}
}
function handleFailure(item) {
// Cold code - rarely executed
console.log('Transform failed for:', item);
logError(new Error('Transform failed'));
notifyAdmin();
item.failed = true;
}
6. Memory-Aware Performance
Object Pooling
// β Creating many short-lived objects
function processFrame() {
const vector = { x: 0, y: 0, z: 0 }; // New object every frame!
// ... use vector
}
// β
Object pooling
class VectorPool {
constructor(size) {
this.pool = [];
this.index = 0;
for (let i = 0; i < size; i++) {
this.pool.push({ x: 0, y: 0, z: 0 });
}
}
acquire() {
const obj = this.pool[this.index];
this.index = (this.index + 1) % this.pool.length;
return obj;
}
reset(obj) {
obj.x = 0;
obj.y = 0;
obj.z = 0;
}
}
const vectorPool = new VectorPool(100);
function processFrame() {
const vector = vectorPool.acquire(); // Reuse!
// ... use vector
vectorPool.reset(vector);
}
Avoid Closure Allocations in Loops
// β New function every iteration
for (let i = 0; i < 1000; i++) {
element.addEventListener('click', () => handle(i));
}
// β
Closure outside loop if possible
function createHandler(index) {
return function handler() {
handle(index);
};
}
for (let i = 0; i < 1000; i++) {
element.addEventListener('click', createHandler(i));
}
// β
β
Even better: Use data attributes + delegation
parent.addEventListener('click', (e) => {
const index = e.target.dataset.index;
if (index) handle(parseInt(index));
});
7. Data Structure Performance
Arrays vs Objects vs Maps
// Arrays: Best for sequential integer-indexed data
const arr = [1, 2, 3, 4, 5];
// Objects: Best for fixed-shape structured data
const obj = { name: 'Alice', age: 30 };
// Maps: Best for dynamic key-value storage
const map = new Map();
map.set(objectKey, value);
// Sets: Best for unique value collections
const set = new Set([1, 2, 3]);
Typed Arrays for Numeric Data
// β Regular array for numbers
const numbers = [1.5, 2.5, 3.5, 4.5]; // Each element is a boxed Number
// β
Typed array
const floats = new Float64Array([1.5, 2.5, 3.5, 4.5]); // Direct memory
const ints = new Int32Array([1, 2, 3, 4]); // Even more compact
Pre-sizing Arrays
// β Array grows dynamically
const arr = [];
for (let i = 0; i < 10000; i++) {
arr.push(i); // May cause multiple reallocations
}
// β
Pre-sized array
const arr = new Array(10000);
for (let i = 0; i < 10000; i++) {
arr[i] = i; // No reallocation needed
}
8. String Performance
String Concatenation
// β Repeated concatenation
let result = '';
for (let i = 0; i < 10000; i++) {
result += 'item' + i + ', '; // Creates many intermediate strings
}
// β
Array join
const parts = [];
for (let i = 0; i < 10000; i++) {
parts.push(`item${i}`);
}
const result = parts.join(', '); // Single final string
// β
Template literals for simple cases
const result = `Hello, ${name}!`; // Efficient for simple interpolation
String Interning
// JavaScript interns string literals
const a = 'hello';
const b = 'hello';
console.log(a === b); // true - same string in memory
// Computed strings might not be interned
const c = 'hel' + 'lo'; // May or may not be interned
9. Function Optimization Patterns
Monomorphic Functions
// β
Keep functions monomorphic
function addNumbers(a, b) {
return a + b;
}
// Always call with same types
addNumbers(1, 2);
addNumbers(3, 4);
// Never: addNumbers("a", "b")
Avoid Excessive Function Parameters
// β Many parameters
function create(a, b, c, d, e, f, g, h) {
// ...
}
// β
Options object
function create(options) {
const { a, b, c, d, e, f, g, h } = options;
// ...
}
Inline-able Functions
// Small, simple functions can be inlined by the engine
function double(x) {
return x * 2;
}
// The engine might inline this into:
// for (let i = 0; i < arr.length; i++) {
// results[i] = arr[i] * 2; // Inlined!
// }
10. Profiling and Measurement
Using Performance API
function measurePerformance(fn, iterations = 1000) {
const start = performance.now();
for (let i = 0; i < iterations; i++) {
fn();
}
const end = performance.now();
const total = end - start;
const perCall = total / iterations;
console.log(`Total: ${total.toFixed(2)}ms`);
console.log(`Per call: ${perCall.toFixed(4)}ms`);
}
Chrome DevTools Profiling
// Profile a section of code
console.profile('My Profile');
// ... code to profile
console.profileEnd('My Profile');
Node.js Profiling
# Run with V8 profiler
node --prof your-script.js
# Process the log
node --prof-process isolate-*.log > processed.txt
Summary
Key Performance Principles
- β’Type Stability: Keep types consistent across function calls
- β’Shape Consistency: Maintain consistent object shapes
- β’Hot/Cold Separation: Keep hot paths clean
- β’Minimize Allocations: Reuse objects when possible
- β’Avoid Deopts: Know what triggers deoptimization
Quick Checklist
- β’ Functions receive consistent types
- β’ Objects have stable shapes
- β’ No
eval()orwithstatements - β’ Try-catch isolated from hot paths
- β’ No property deletion on hot objects
- β’ Arrays are properly sized
- β’ Typed arrays for numeric data
Practice Exercises
- β’Identify deoptimization causes in sample code
- β’Refactor code to be more optimization-friendly
- β’Profile and compare different implementations
- β’Build a performance testing framework
- β’Analyze real-world performance bottlenecks
Next Steps
- β’Study browser performance APIs in detail
- β’Learn about Web Workers for CPU-intensive tasks
- β’Explore WebAssembly for maximum performance
- β’Practice with performance profiling tools