javascript
examples
examples.js⚡javascript
/**
* ============================================================
* 22.1 ENGINE ARCHITECTURE - EXAMPLES
* ============================================================
*
* Practical demonstrations of JavaScript engine concepts
* including parsing, optimization, and performance patterns.
*/
/**
* ============================================================
* EXAMPLE 1: LAZY PARSING DEMONSTRATION
* ============================================================
*
* Engines use lazy parsing to speed up initial load.
* Inner functions are pre-parsed but not fully compiled
* until they're actually called.
*/
console.log('=== Example 1: Lazy Parsing ===\n');
// This outer function is eagerly parsed (top-level)
function outerFunction() {
console.log('Outer function executed');
// This inner function is lazily parsed
// Only pre-parsed initially, fully parsed when called
function innerFunction() {
// Complex logic here would only be fully parsed
// when innerFunction is actually invoked
return Array.from({ length: 1000 }, (_, i) => i * 2)
.filter((x) => x % 4 === 0)
.reduce((a, b) => a + b, 0);
}
// Only now is innerFunction fully parsed and compiled
return innerFunction();
}
console.log('Result:', outerFunction());
// IIFE - Immediately Invoked, so eagerly parsed
const immediateResult = (function () {
// This is eagerly parsed because it's called immediately
return 'IIFE result';
})();
console.log('IIFE:', immediateResult);
console.log();
/**
* ============================================================
* EXAMPLE 2: UNDERSTANDING HOT CODE
* ============================================================
*
* Functions that run many times become "hot" and get
* optimized by the JIT compiler.
*/
console.log('=== Example 2: Hot Code Detection ===\n');
// This function will become "hot" after many calls
function addNumbers(a, b) {
return a + b;
}
// Warm up the function - making it "hot"
console.log('Warming up function with consistent types...');
const warmupStart = performance.now();
for (let i = 0; i < 100000; i++) {
addNumbers(i, i + 1); // Always integers
}
const warmupEnd = performance.now();
console.log(`Warmup time: ${(warmupEnd - warmupStart).toFixed(2)}ms`);
// Now the function is optimized for integer addition
console.log("Function is now 'hot' and optimized for integers");
console.log('Result:', addNumbers(100, 200));
console.log();
/**
* ============================================================
* EXAMPLE 3: TYPE STABILITY
* ============================================================
*
* Keeping types stable allows engines to optimize better.
* Type changes can cause deoptimization.
*/
console.log('=== Example 3: Type Stability ===\n');
// GOOD: Consistent types
function multiplyStable(x, y) {
return x * y;
}
// Train with consistent types
for (let i = 0; i < 10000; i++) {
multiplyStable(i, 2); // Always numbers
}
console.log('Stable function result:', multiplyStable(5, 3));
// BAD: Mixed types cause deoptimization
function multiplyUnstable(x, y) {
return x * y;
}
// This pattern causes optimization issues
multiplyUnstable(5, 3); // Numbers
multiplyUnstable('5', 3); // String coercion
multiplyUnstable(5, '3'); // String coercion
multiplyUnstable(null, 3); // null coercion
console.log('Unstable function (may deoptimize):', multiplyUnstable(5, 3));
console.log();
/**
* ============================================================
* EXAMPLE 4: FUNCTION INLINING
* ============================================================
*
* The JIT compiler can inline small functions, eliminating
* function call overhead.
*/
console.log('=== Example 4: Function Inlining ===\n');
// Small function - candidate for inlining
function square(x) {
return x * x;
}
// Using the function in a loop
function calculateSquares(n) {
let sum = 0;
for (let i = 0; i < n; i++) {
sum += square(i); // This call may be inlined
}
return sum;
}
// After optimization, this might become:
function calculateSquaresInlined(n) {
let sum = 0;
for (let i = 0; i < n; i++) {
sum += i * i; // Inlined - no function call
}
return sum;
}
console.log('With function:', calculateSquares(1000));
console.log('Inlined (manual):', calculateSquaresInlined(1000));
// Benchmark
const start1 = performance.now();
for (let i = 0; i < 10000; i++) calculateSquares(100);
console.log(`With function: ${(performance.now() - start1).toFixed(2)}ms`);
const start2 = performance.now();
for (let i = 0; i < 10000; i++) calculateSquaresInlined(100);
console.log(`Inlined: ${(performance.now() - start2).toFixed(2)}ms`);
console.log();
/**
* ============================================================
* EXAMPLE 5: ESCAPE ANALYSIS
* ============================================================
*
* Objects that don't "escape" a function can be
* stack-allocated instead of heap-allocated.
*/
console.log('=== Example 5: Escape Analysis ===\n');
// Object DOES escape - must be heap allocated
function createPointEscapes(x, y) {
return { x, y }; // Object leaves the function
}
// Object DOESN'T escape - can be stack allocated
function distanceNoEscape(x1, y1, x2, y2) {
// This object is created and used only within the function
const point1 = { x: x1, y: y1 };
const point2 = { x: x2, y: y2 };
// After optimization, the engine might not allocate
// objects at all - just use the values directly
const dx = point2.x - point1.x;
const dy = point2.y - point1.y;
return Math.sqrt(dx * dx + dy * dy);
}
console.log('Escaping object:', createPointEscapes(3, 4));
console.log('Non-escaping (optimized):', distanceNoEscape(0, 0, 3, 4));
console.log();
/**
* ============================================================
* EXAMPLE 6: CONSTANT FOLDING
* ============================================================
*
* The compiler can compute constant expressions at compile time.
*/
console.log('=== Example 6: Constant Folding ===\n');
// These constants can be computed at compile time
const SECONDS_PER_MINUTE = 60;
const MINUTES_PER_HOUR = 60;
const HOURS_PER_DAY = 24;
// This multiplication is done at compile time, not runtime
const SECONDS_PER_DAY = SECONDS_PER_MINUTE * MINUTES_PER_HOUR * HOURS_PER_DAY;
console.log('Seconds per day (folded):', SECONDS_PER_DAY);
// Function with constant expressions
function getCircleArea(radius) {
// Math.PI * 2 might be folded to ~6.283185307179586
const circumference = Math.PI * 2 * radius;
// Math.PI is constant, multiplication can be optimized
const area = Math.PI * radius * radius;
return { circumference, area };
}
console.log('Circle (r=5):', getCircleArea(5));
console.log();
/**
* ============================================================
* EXAMPLE 7: DEAD CODE ELIMINATION
* ============================================================
*
* The compiler removes code that has no effect.
*/
console.log('=== Example 7: Dead Code Elimination ===\n');
function withDeadCode(x) {
let result = x * 2;
// This is dead code - never used
let unused = x * 3;
let alsoUnused = Math.sqrt(x);
// This is dead code - condition always false
if (false) {
result = x * 100;
}
// This is unreachable code
return result;
// Everything below is unreachable
result = x * 4;
console.log('Never printed');
}
// After optimization, this becomes essentially:
function optimized(x) {
return x * 2;
}
console.log('With dead code:', withDeadCode(5));
console.log('Optimized:', optimized(5));
console.log();
/**
* ============================================================
* EXAMPLE 8: LOOP OPTIMIZATION
* ============================================================
*
* Engines optimize loops in various ways:
* - Loop unrolling
* - Loop invariant code motion
* - Bounds check elimination
*/
console.log('=== Example 8: Loop Optimization ===\n');
// Loop invariant code motion
function loopWithInvariant(arr, multiplier) {
const result = [];
const len = arr.length; // Hoisted out of loop
for (let i = 0; i < len; i++) {
// multiplier * 2 is invariant - computed once
result.push(arr[i] * (multiplier * 2));
}
return result;
}
// After optimization, equivalent to:
function loopOptimized(arr, multiplier) {
const result = [];
const len = arr.length;
const factor = multiplier * 2; // Computed once
for (let i = 0; i < len; i++) {
result.push(arr[i] * factor);
}
return result;
}
const testArr = [1, 2, 3, 4, 5];
console.log('With invariant:', loopWithInvariant(testArr, 3));
console.log('Optimized:', loopOptimized(testArr, 3));
// Loop unrolling example (conceptual)
function sumUnrolled(arr) {
let sum = 0;
const len = arr.length;
// Process 4 elements at a time
let i = 0;
for (; i + 3 < len; i += 4) {
sum += arr[i] + arr[i + 1] + arr[i + 2] + arr[i + 3];
}
// Handle remaining elements
for (; i < len; i++) {
sum += arr[i];
}
return sum;
}
console.log('Unrolled sum:', sumUnrolled([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]));
console.log();
/**
* ============================================================
* EXAMPLE 9: DEOPTIMIZATION TRIGGERS
* ============================================================
*
* These patterns can cause deoptimization.
* Avoid them in performance-critical code.
*/
console.log('=== Example 9: Deoptimization Triggers ===\n');
// 1. Type changes
function processValue(val) {
return val * 2;
}
// Good: consistent types
for (let i = 0; i < 1000; i++) {
processValue(i);
}
console.log('After numbers:', processValue(50));
// Bad: type change causes deopt
// processValue("hello"); // Uncomment to trigger deopt
// 2. Using arguments object
function useArguments() {
// Using 'arguments' can prevent optimization
// return Array.from(arguments).reduce((a, b) => a + b, 0);
// Better: use rest parameters
return [...arguments].reduce((a, b) => a + b, 0);
}
// Even better: use rest parameters directly
function useRest(...args) {
return args.reduce((a, b) => a + b, 0);
}
console.log('With arguments:', useArguments(1, 2, 3, 4, 5));
console.log('With rest:', useRest(1, 2, 3, 4, 5));
// 3. try-catch in hot code
function withTryCatch(x) {
try {
return x * 2;
} catch (e) {
return 0;
}
}
// Better: keep try-catch separate
function safeMath(x) {
return x * 2;
}
function withErrorHandling(x) {
try {
return safeMath(x);
} catch (e) {
return 0;
}
}
console.log('Inline try-catch:', withTryCatch(5));
console.log('Separated:', withErrorHandling(5));
console.log();
/**
* ============================================================
* EXAMPLE 10: HIDDEN CLASS TRANSITIONS PREVIEW
* ============================================================
*
* Objects with the same structure share "hidden classes".
* This is covered in detail in section 22.2.
*/
console.log('=== Example 10: Hidden Class Preview ===\n');
// Good: All objects have same shape (same hidden class)
function createPoint(x, y) {
return { x, y }; // Same property order
}
const p1 = createPoint(1, 2);
const p2 = createPoint(3, 4);
const p3 = createPoint(5, 6);
console.log('Points with same hidden class:');
console.log(p1, p2, p3);
// Bad: Objects created with different property orders
// have different hidden classes
function createPointBad(x, y, useZ) {
const point = {};
if (useZ) {
point.z = 0; // Different order
}
point.x = x;
point.y = y;
return point;
}
const bad1 = createPointBad(1, 2, false); // { x, y }
const bad2 = createPointBad(3, 4, true); // { z, x, y }
console.log('\nPoints with different hidden classes:');
console.log(bad1, bad2);
console.log();
/**
* ============================================================
* EXAMPLE 11: BENCHMARKING PATTERNS
* ============================================================
*
* How to properly benchmark JavaScript code considering
* JIT compilation effects.
*/
console.log('=== Example 11: Proper Benchmarking ===\n');
function benchmarkFunction(fn, iterations = 100000, warmup = 10000) {
// Warmup phase - let the JIT optimize
console.log(`Warming up with ${warmup} iterations...`);
for (let i = 0; i < warmup; i++) {
fn(i);
}
// Force garbage collection if possible (Node.js with --expose-gc)
if (global.gc) {
global.gc();
}
// Actual benchmark
console.log(`Running ${iterations} iterations...`);
const start = performance.now();
for (let i = 0; i < iterations; i++) {
fn(i);
}
const end = performance.now();
const totalTime = end - start;
const opsPerSecond = (iterations / totalTime) * 1000;
return {
totalTime: totalTime.toFixed(2) + 'ms',
opsPerSecond: opsPerSecond.toFixed(0),
avgTimePerOp: ((totalTime / iterations) * 1000000).toFixed(2) + 'ns',
};
}
function testFunction(x) {
return x * x + x / 2 - (x % 3);
}
const results = benchmarkFunction(testFunction);
console.log('Benchmark results:', results);
console.log();
/**
* ============================================================
* EXAMPLE 12: VIEWING ENGINE INTERNALS
* ============================================================
*
* Ways to inspect what the engine is doing.
* (Some require special Node.js flags)
*/
console.log('=== Example 12: Engine Inspection Commands ===\n');
console.log('V8 inspection flags (run with Node.js):\n');
console.log('1. View bytecode:');
console.log(
' node --print-bytecode --print-bytecode-filter=functionName script.js\n'
);
console.log('2. View optimizations:');
console.log(' node --trace-opt script.js\n');
console.log('3. View deoptimizations:');
console.log(' node --trace-deopt script.js\n');
console.log('4. View hidden class transitions:');
console.log(' node --trace-ic script.js\n');
console.log('5. View garbage collection:');
console.log(' node --trace-gc script.js\n');
console.log('6. Generate V8 optimization log:');
console.log(' node --trace-turbo script.js\n');
console.log('7. Profile with inspector:');
console.log(' node --inspect-brk script.js\n');
// Example of checking optimization status (V8 native)
// Note: This requires Node.js with --allow-natives-syntax
/*
function exampleForOptCheck(x) {
return x + 1;
}
// Warm up
for (let i = 0; i < 100000; i++) {
exampleForOptCheck(i);
}
// Check optimization status
console.log("Is optimized:", %IsCrankshafted(exampleForOptCheck));
*/
console.log('=== Examples Complete ===\n');