Docs

README

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

  1. β€’Type Stability: Keep types consistent across function calls
  2. β€’Shape Consistency: Maintain consistent object shapes
  3. β€’Hot/Cold Separation: Keep hot paths clean
  4. β€’Minimize Allocations: Reuse objects when possible
  5. β€’Avoid Deopts: Know what triggers deoptimization

Quick Checklist

  • β€’ Functions receive consistent types
  • β€’ Objects have stable shapes
  • β€’ No eval() or with statements
  • β€’ Try-catch isolated from hot paths
  • β€’ No property deletion on hot objects
  • β€’ Arrays are properly sized
  • β€’ Typed arrays for numeric data

Practice Exercises

  1. β€’Identify deoptimization causes in sample code
  2. β€’Refactor code to be more optimization-friendly
  3. β€’Profile and compare different implementations
  4. β€’Build a performance testing framework
  5. β€’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
README - JavaScript Tutorial | DeepML