Created
December 26, 2025 05:30
-
-
Save edwardkenfox/2b1a4315486551ea4d96803fbe92eb3a to your computer and use it in GitHub Desktop.
Benchmark to validate V8 code caching behavior with HTTP 304 vs 200 responses
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * V8 Code Cache Benchmark with Chrome Tracing | |
| * | |
| * This script uses Chrome DevTools Protocol to capture V8 compile traces | |
| * and measure the actual compile time differences between cache states. | |
| * | |
| * Usage: | |
| * npm install puppeteer | |
| * node benchmark-with-tracing.js | |
| */ | |
| import puppeteer from 'puppeteer'; | |
| import fs from 'fs'; | |
| const SERVER_URL = process.env.SERVER_URL || 'http://localhost:3000'; | |
| const RUNS = 5; | |
| const OUTPUT_DIR = './trace-output'; | |
| // Ensure output directory exists | |
| if (!fs.existsSync(OUTPUT_DIR)) { | |
| fs.mkdirSync(OUTPUT_DIR, { recursive: true }); | |
| } | |
| async function sleep(ms) { | |
| return new Promise(resolve => setTimeout(resolve, ms)); | |
| } | |
| function analyzeTrace(traceEvents, scriptUrl) { | |
| const compileEvents = traceEvents.filter(event => | |
| event.name === 'v8.compile' && | |
| event.args?.fileName?.includes(scriptUrl) | |
| ); | |
| const results = { | |
| compileEvents: [], | |
| totalCompileTime: 0, | |
| cacheStatus: 'unknown', | |
| }; | |
| for (const event of compileEvents) { | |
| const compileInfo = { | |
| duration: event.dur / 1000, // Convert to ms | |
| fileName: event.args.fileName, | |
| }; | |
| // Check for cache-related metadata | |
| if (event.args.cacheConsumeOptions) { | |
| compileInfo.cacheAction = 'consume'; | |
| compileInfo.cacheSize = event.args.consumedCacheSize; | |
| results.cacheStatus = 'hot'; | |
| } else if (event.args.cacheProduceOptions) { | |
| compileInfo.cacheAction = 'produce'; | |
| compileInfo.cacheSize = event.args.producedCacheSize; | |
| results.cacheStatus = 'warm'; | |
| } else { | |
| compileInfo.cacheAction = 'none'; | |
| if (results.cacheStatus === 'unknown') { | |
| results.cacheStatus = 'cold'; | |
| } | |
| } | |
| results.compileEvents.push(compileInfo); | |
| results.totalCompileTime += compileInfo.duration; | |
| } | |
| return results; | |
| } | |
| async function runWithTracing(browser, runNumber) { | |
| const page = await browser.newPage(); | |
| const client = await page.createCDPSession(); | |
| // Start tracing with V8 category | |
| await client.send('Tracing.start', { | |
| traceConfig: { | |
| includedCategories: ['v8', 'devtools.timeline'], | |
| excludedCategories: ['__metadata'], | |
| }, | |
| transferMode: 'ReturnAsStream', | |
| }); | |
| let httpStatus = null; | |
| page.on('response', response => { | |
| if (response.url().includes('bundle.js')) { | |
| httpStatus = response.status(); | |
| } | |
| }); | |
| // Navigate to page | |
| await page.goto(SERVER_URL, { waitUntil: 'networkidle0' }); | |
| await sleep(500); | |
| // Get page-level benchmark results | |
| const pageResults = await page.evaluate(() => { | |
| if (window.BenchmarkResults) { | |
| return window.BenchmarkResults.results; | |
| } | |
| return null; | |
| }); | |
| // Stop tracing and get data | |
| const traceData = await client.send('Tracing.end'); | |
| // Collect trace chunks | |
| const chunks = []; | |
| await new Promise((resolve) => { | |
| client.on('Tracing.tracingComplete', async (params) => { | |
| if (params.stream) { | |
| let eof = false; | |
| while (!eof) { | |
| const { data, base64Encoded } = await client.send('IO.read', { handle: params.stream }); | |
| chunks.push(base64Encoded ? Buffer.from(data, 'base64').toString() : data); | |
| const eofResult = await client.send('IO.read', { handle: params.stream }); | |
| eof = eofResult.eof; | |
| if (!eofResult.eof) { | |
| chunks.push(eofResult.base64Encoded ? Buffer.from(eofResult.data, 'base64').toString() : eofResult.data); | |
| } | |
| } | |
| await client.send('IO.close', { handle: params.stream }); | |
| } | |
| resolve(); | |
| }); | |
| }); | |
| // Parse trace data | |
| let traceEvents = []; | |
| try { | |
| const traceJson = JSON.parse(chunks.join('')); | |
| traceEvents = traceJson.traceEvents || []; | |
| } catch (e) { | |
| console.warn('Could not parse trace data:', e.message); | |
| } | |
| // Save trace file for manual inspection | |
| const traceFile = `${OUTPUT_DIR}/trace-run-${runNumber}.json`; | |
| fs.writeFileSync(traceFile, JSON.stringify({ traceEvents }, null, 2)); | |
| // Analyze the trace | |
| const traceAnalysis = analyzeTrace(traceEvents, 'bundle.js'); | |
| await page.close(); | |
| return { | |
| run: runNumber, | |
| httpStatus, | |
| pageResults, | |
| traceAnalysis, | |
| traceFile, | |
| }; | |
| } | |
| async function main() { | |
| console.log('V8 Code Cache Benchmark with Chrome Tracing'); | |
| console.log('============================================'); | |
| console.log(`Server URL: ${SERVER_URL}`); | |
| console.log(`Number of runs: ${RUNS}`); | |
| console.log(`Trace output: ${OUTPUT_DIR}/`); | |
| console.log(''); | |
| // Check server | |
| try { | |
| const response = await fetch(SERVER_URL); | |
| if (!response.ok) throw new Error('Server not responding'); | |
| } catch (error) { | |
| console.error(`Error: Cannot connect to server at ${SERVER_URL}`); | |
| console.error('Please start the server first: node server.js'); | |
| process.exit(1); | |
| } | |
| const browser = await puppeteer.launch({ | |
| headless: true, | |
| args: [ | |
| '--no-sandbox', | |
| '--disable-setuid-sandbox', | |
| '--enable-tracing', | |
| ] | |
| }); | |
| const results = []; | |
| for (let i = 1; i <= RUNS; i++) { | |
| console.log(`\nRun ${i}/${RUNS}...`); | |
| try { | |
| const result = await runWithTracing(browser, i); | |
| results.push(result); | |
| console.log(` HTTP Status: ${result.httpStatus}`); | |
| console.log(` V8 Cache Status: ${result.traceAnalysis.cacheStatus}`); | |
| console.log(` V8 Compile Time: ${result.traceAnalysis.totalCompileTime.toFixed(2)} ms`); | |
| console.log(` Page Total Time: ${result.pageResults?.totalTime?.toFixed(2)} ms`); | |
| console.log(` Trace saved: ${result.traceFile}`); | |
| // Log cache-specific details | |
| for (const event of result.traceAnalysis.compileEvents) { | |
| if (event.cacheAction !== 'none') { | |
| console.log(` Cache ${event.cacheAction}: ${event.cacheSize} bytes`); | |
| } | |
| } | |
| } catch (error) { | |
| console.error(` Error: ${error.message}`); | |
| results.push({ run: i, error: error.message }); | |
| } | |
| await sleep(300); | |
| } | |
| await browser.close(); | |
| // Summary | |
| console.log('\n'); | |
| console.log('='.repeat(50)); | |
| console.log('SUMMARY'); | |
| console.log('='.repeat(50)); | |
| const validResults = results.filter(r => !r.error); | |
| const byStatus = { | |
| cold: validResults.filter(r => r.traceAnalysis.cacheStatus === 'cold'), | |
| warm: validResults.filter(r => r.traceAnalysis.cacheStatus === 'warm'), | |
| hot: validResults.filter(r => r.traceAnalysis.cacheStatus === 'hot'), | |
| }; | |
| for (const [status, runs] of Object.entries(byStatus)) { | |
| if (runs.length > 0) { | |
| const avgCompile = runs.reduce((sum, r) => sum + r.traceAnalysis.totalCompileTime, 0) / runs.length; | |
| const avgTotal = runs.reduce((sum, r) => sum + (r.pageResults?.totalTime || 0), 0) / runs.length; | |
| console.log(`\n${status.toUpperCase()} runs (${runs.length}):`); | |
| console.log(` Average V8 Compile Time: ${avgCompile.toFixed(2)} ms`); | |
| console.log(` Average Page Total Time: ${avgTotal.toFixed(2)} ms`); | |
| } | |
| } | |
| // Calculate improvement | |
| if (byStatus.cold.length > 0 && byStatus.hot.length > 0) { | |
| const coldAvg = byStatus.cold.reduce((sum, r) => sum + r.traceAnalysis.totalCompileTime, 0) / byStatus.cold.length; | |
| const hotAvg = byStatus.hot.reduce((sum, r) => sum + r.traceAnalysis.totalCompileTime, 0) / byStatus.hot.length; | |
| const improvement = ((coldAvg - hotAvg) / coldAvg * 100).toFixed(1); | |
| const timeSaved = (coldAvg - hotAvg).toFixed(2); | |
| console.log(`\n📈 V8 Compile Time Improvement (hot vs cold):`); | |
| console.log(` ${improvement}% faster (${timeSaved} ms saved)`); | |
| } | |
| // Save summary | |
| const summaryFile = `${OUTPUT_DIR}/summary.json`; | |
| fs.writeFileSync(summaryFile, JSON.stringify({ | |
| config: { serverUrl: SERVER_URL, runs: RUNS }, | |
| results, | |
| summary: byStatus, | |
| }, null, 2)); | |
| console.log(`\nFull results saved to: ${summaryFile}`); | |
| } | |
| main().catch(console.error); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>V8 Code Cache Benchmark - 304 vs 200</title> | |
| <style> | |
| * { | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; | |
| max-width: 900px; | |
| margin: 0 auto; | |
| padding: 20px; | |
| background: #f5f5f5; | |
| color: #333; | |
| } | |
| h1 { | |
| color: #1a73e8; | |
| border-bottom: 2px solid #1a73e8; | |
| padding-bottom: 10px; | |
| } | |
| h2 { | |
| color: #555; | |
| margin-top: 30px; | |
| } | |
| .card { | |
| background: white; | |
| border-radius: 8px; | |
| padding: 20px; | |
| margin: 15px 0; | |
| box-shadow: 0 2px 4px rgba(0,0,0,0.1); | |
| } | |
| .results { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); | |
| gap: 15px; | |
| } | |
| .metric { | |
| background: #e8f0fe; | |
| padding: 15px; | |
| border-radius: 6px; | |
| text-align: center; | |
| } | |
| .metric-value { | |
| font-size: 28px; | |
| font-weight: bold; | |
| color: #1a73e8; | |
| } | |
| .metric-label { | |
| font-size: 14px; | |
| color: #666; | |
| margin-top: 5px; | |
| } | |
| button { | |
| background: #1a73e8; | |
| color: white; | |
| border: none; | |
| padding: 12px 24px; | |
| border-radius: 6px; | |
| font-size: 16px; | |
| cursor: pointer; | |
| margin: 5px; | |
| } | |
| button:hover { | |
| background: #1557b0; | |
| } | |
| button:disabled { | |
| background: #ccc; | |
| cursor: not-allowed; | |
| } | |
| .history { | |
| max-height: 300px; | |
| overflow-y: auto; | |
| } | |
| table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| } | |
| th, td { | |
| padding: 10px; | |
| text-align: left; | |
| border-bottom: 1px solid #ddd; | |
| } | |
| th { | |
| background: #f0f0f0; | |
| position: sticky; | |
| top: 0; | |
| } | |
| .run-cold { background: #ffebee; } | |
| .run-warm { background: #fff3e0; } | |
| .run-hot { background: #e8f5e9; } | |
| code { | |
| background: #f0f0f0; | |
| padding: 2px 6px; | |
| border-radius: 3px; | |
| font-family: 'Monaco', 'Consolas', monospace; | |
| } | |
| pre { | |
| background: #263238; | |
| color: #aed581; | |
| padding: 15px; | |
| border-radius: 6px; | |
| overflow-x: auto; | |
| } | |
| .info-box { | |
| background: #e3f2fd; | |
| border-left: 4px solid #1a73e8; | |
| padding: 15px; | |
| margin: 15px 0; | |
| } | |
| .warning-box { | |
| background: #fff3e0; | |
| border-left: 4px solid #ff9800; | |
| padding: 15px; | |
| margin: 15px 0; | |
| } | |
| #resourceTiming { | |
| font-family: monospace; | |
| white-space: pre; | |
| background: #f5f5f5; | |
| padding: 10px; | |
| border-radius: 4px; | |
| overflow-x: auto; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>🚀 V8 Code Cache Benchmark</h1> | |
| <p>Testing how HTTP 304 Not Modified responses preserve V8's code cache vs 200 OK responses that clear it.</p> | |
| <div class="card"> | |
| <h2>Current Run Results</h2> | |
| <div class="results" id="results"> | |
| <div class="metric"> | |
| <div class="metric-value" id="totalTime">-</div> | |
| <div class="metric-label">Total Time (ms)</div> | |
| </div> | |
| <div class="metric"> | |
| <div class="metric-value" id="compilationTime">-</div> | |
| <div class="metric-label">Load + Parse + Compile (ms)</div> | |
| </div> | |
| <div class="metric"> | |
| <div class="metric-value" id="executionTime">-</div> | |
| <div class="metric-label">Execution (ms)</div> | |
| </div> | |
| <div class="metric"> | |
| <div class="metric-value" id="runType">-</div> | |
| <div class="metric-label">Run Type</div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="card"> | |
| <h2>Controls</h2> | |
| <p> | |
| <button onclick="reloadPage()">🔄 Reload Page</button> | |
| <button onclick="hardReload()">⚡ Hard Reload (Shift+Refresh)</button> | |
| <button onclick="clearHistory()">🗑️ Clear History</button> | |
| </p> | |
| <p> | |
| <strong>Run #<span id="runNumber">1</span></strong> | | |
| Server mode: <code id="serverMode">Checking...</code> | |
| </p> | |
| </div> | |
| <div class="card"> | |
| <h2>Resource Timing</h2> | |
| <div id="resourceTiming">Loading...</div> | |
| </div> | |
| <div class="card"> | |
| <h2>Run History</h2> | |
| <div class="history"> | |
| <table id="historyTable"> | |
| <thead> | |
| <tr> | |
| <th>#</th> | |
| <th>Total (ms)</th> | |
| <th>Compile (ms)</th> | |
| <th>Execute (ms)</th> | |
| <th>Cache Status</th> | |
| <th>Time</th> | |
| </tr> | |
| </thead> | |
| <tbody id="historyBody"> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| <div class="card"> | |
| <h2>How to Use This Benchmark</h2> | |
| <div class="info-box"> | |
| <strong>Goal:</strong> Demonstrate that HTTP 304 responses preserve V8's compiled bytecode cache, | |
| while 200 responses (even with identical content) clear the cache and force recompilation. | |
| </div> | |
| <h3>Test Procedure</h3> | |
| <ol> | |
| <li><strong>Start with Normal Server (supports 304):</strong> | |
| <pre>node server.js</pre> | |
| </li> | |
| <li><strong>Load the page 3+ times:</strong> | |
| <ul> | |
| <li><strong>Run 1 (Cold):</strong> Downloads & compiles JS, no cache</li> | |
| <li><strong>Run 2 (Warm):</strong> 304 response, compiles & creates code cache</li> | |
| <li><strong>Run 3+ (Hot):</strong> 304 response, uses cached bytecode (fast!)</li> | |
| </ul> | |
| </li> | |
| <li><strong>Compare with Force-200 mode:</strong> | |
| <pre>node server.js --force-200</pre> | |
| Every load returns 200, clearing the code cache each time. | |
| </li> | |
| </ol> | |
| <h3>Verifying with Chrome DevTools</h3> | |
| <ol> | |
| <li>Open DevTools → Network tab</li> | |
| <li>Look at <code>bundle.js</code> response status: | |
| <ul> | |
| <li><code>200</code> = Fresh download (cache cleared)</li> | |
| <li><code>304</code> = Not Modified (cache preserved)</li> | |
| </ul> | |
| </li> | |
| </ol> | |
| <h3>Verifying with chrome://tracing</h3> | |
| <ol> | |
| <li>Open <code>chrome://tracing</code> in a new tab</li> | |
| <li>Click "Record" and select the "v8" category</li> | |
| <li>Load this benchmark page</li> | |
| <li>Stop recording and search for <code>v8.compile</code></li> | |
| <li>Look for these metadata fields: | |
| <ul> | |
| <li><code>cacheProduceOptions</code> / <code>producedCacheSize</code> = Warm run (producing cache)</li> | |
| <li><code>cacheConsumeOptions</code> / <code>consumedCacheSize</code> = Hot run (using cache)</li> | |
| <li>Neither = Cold run (no cache)</li> | |
| </ul> | |
| </li> | |
| </ol> | |
| <div class="warning-box"> | |
| <strong>Note:</strong> The JavaScript timing measurements above capture the overall | |
| script load-to-execute time. For precise V8 compile times, use <code>chrome://tracing</code>. | |
| The timing differences you see here include network I/O, parsing, compilation, and execution. | |
| </div> | |
| </div> | |
| <div class="card"> | |
| <h2>Expected Results</h2> | |
| <table> | |
| <tr> | |
| <th>Scenario</th> | |
| <th>HTTP Status</th> | |
| <th>V8 Cache</th> | |
| <th>Expected Speed</th> | |
| </tr> | |
| <tr class="run-cold"> | |
| <td>Cold Run (1st load)</td> | |
| <td>200 OK</td> | |
| <td>None</td> | |
| <td>Slowest (full compile)</td> | |
| </tr> | |
| <tr class="run-warm"> | |
| <td>Warm Run (2nd load, 304)</td> | |
| <td>304 Not Modified</td> | |
| <td>Producing</td> | |
| <td>Medium (compile + cache)</td> | |
| </tr> | |
| <tr class="run-hot"> | |
| <td>Hot Run (3rd+ load, 304)</td> | |
| <td>304 Not Modified</td> | |
| <td>Consuming</td> | |
| <td>Fastest (skip compile)</td> | |
| </tr> | |
| <tr class="run-cold"> | |
| <td>Force 200 (any load)</td> | |
| <td>200 OK</td> | |
| <td>Cleared!</td> | |
| <td>Slowest (recompile every time)</td> | |
| </tr> | |
| </table> | |
| </div> | |
| <script> | |
| // Load history from localStorage | |
| let history = JSON.parse(localStorage.getItem('v8CacheBenchmarkHistory') || '[]'); | |
| let runNumber = history.length + 1; | |
| document.getElementById('runNumber').textContent = runNumber; | |
| // Track timing BEFORE script loads | |
| const scriptLoadStart = performance.now(); | |
| let scriptLoadEnd = null; | |
| let scriptExecuteEnd = null; | |
| function updateResults() { | |
| if (window.BenchmarkResults && scriptLoadEnd !== null) { | |
| // Get Resource Timing data for bundle.js (find the script entry, not the fetch/HEAD request) | |
| const entries = performance.getEntriesByType('resource'); | |
| const bundleEntry = entries | |
| .filter(e => e.name.includes('bundle.js')) | |
| .find(e => e.initiatorType === 'script') || | |
| entries.find(e => e.name.includes('bundle.js')); | |
| // Calculate times: | |
| // - totalTime: from our request start to script fully executed | |
| // - executeTime: the runBenchmark() execution (from bundle.js internal timing) | |
| // - loadParseCompileTime: totalTime - executeTime (network + parse + compile) | |
| const totalTime = scriptExecuteEnd - scriptLoadStart; | |
| const executeTime = window.BenchmarkResults.results.executionTime; | |
| const loadParseCompileTime = totalTime - executeTime; | |
| // Detect cache status from Resource Timing (based on actual transfer, not run number) | |
| // 304 responses transfer only headers (~300 bytes), while 200 transfers full body (~20KB) | |
| let cacheStatus = 'Cold'; | |
| if (bundleEntry) { | |
| const bodySize = bundleEntry.decodedBodySize || bundleEntry.encodedBodySize || 0; | |
| const wasFromCache = bundleEntry.transferSize < bodySize * 0.5; // 304 = headers only, much smaller than body | |
| if (wasFromCache && bodySize > 0) { | |
| // 304 response - Warm = cache being built, Hot = cache being used | |
| cacheStatus = runNumber <= 2 ? 'Warm' : 'Hot'; | |
| } else { | |
| // Fresh 200 response | |
| cacheStatus = 'Cold'; | |
| } | |
| } | |
| document.getElementById('totalTime').textContent = totalTime.toFixed(2); | |
| document.getElementById('compilationTime').textContent = loadParseCompileTime.toFixed(2); | |
| document.getElementById('executionTime').textContent = executeTime.toFixed(2); | |
| document.getElementById('runType').textContent = cacheStatus; | |
| // Save to history | |
| const entry = { | |
| run: runNumber, | |
| total: totalTime, | |
| compile: loadParseCompileTime, | |
| execute: executeTime, | |
| runType: cacheStatus, | |
| transferSize: bundleEntry ? bundleEntry.transferSize : -1, | |
| timestamp: new Date().toISOString() | |
| }; | |
| history.push(entry); | |
| localStorage.setItem('v8CacheBenchmarkHistory', JSON.stringify(history)); | |
| renderHistory(); | |
| } | |
| } | |
| function renderHistory() { | |
| const tbody = document.getElementById('historyBody'); | |
| tbody.innerHTML = ''; | |
| history.slice().reverse().forEach(entry => { | |
| const tr = document.createElement('tr'); | |
| tr.className = `run-${entry.runType.toLowerCase()}`; | |
| tr.innerHTML = ` | |
| <td>${entry.run}</td> | |
| <td>${entry.total.toFixed(2)}</td> | |
| <td>${entry.compile.toFixed(2)}</td> | |
| <td>${entry.execute.toFixed(2)}</td> | |
| <td>${entry.runType}</td> | |
| <td>${new Date(entry.timestamp).toLocaleTimeString()}</td> | |
| `; | |
| tbody.appendChild(tr); | |
| }); | |
| } | |
| function reloadPage() { | |
| location.reload(); | |
| } | |
| function hardReload() { | |
| location.reload(true); | |
| } | |
| function clearHistory() { | |
| if (confirm('Clear run history?')) { | |
| history = []; | |
| localStorage.removeItem('v8CacheBenchmarkHistory'); | |
| runNumber = 1; | |
| document.getElementById('runNumber').textContent = runNumber; | |
| renderHistory(); | |
| } | |
| } | |
| // Check server mode | |
| fetch('./bundle.js', { method: 'HEAD' }) | |
| .then(res => { | |
| document.getElementById('serverMode').textContent = | |
| `HTTP ${res.status} (${res.status === 304 ? 'Cache Preserved' : 'Fresh Response'})`; | |
| }) | |
| .catch(() => { | |
| document.getElementById('serverMode').textContent = 'Unknown'; | |
| }); | |
| // Display Resource Timing | |
| function displayResourceTiming() { | |
| const entries = performance.getEntriesByType('resource'); | |
| const bundleEntry = entries | |
| .filter(e => e.name.includes('bundle.js')) | |
| .find(e => e.initiatorType === 'script') || | |
| entries.find(e => e.name.includes('bundle.js')); | |
| if (bundleEntry) { | |
| const info = { | |
| 'Name': bundleEntry.name.split('/').pop(), | |
| 'Transfer Size': `${bundleEntry.transferSize} bytes`, | |
| 'Encoded Body Size': `${bundleEntry.encodedBodySize} bytes`, | |
| 'DNS Lookup': `${(bundleEntry.domainLookupEnd - bundleEntry.domainLookupStart).toFixed(2)} ms`, | |
| 'TCP Connect': `${(bundleEntry.connectEnd - bundleEntry.connectStart).toFixed(2)} ms`, | |
| 'Request → Response Start': `${(bundleEntry.responseStart - bundleEntry.requestStart).toFixed(2)} ms`, | |
| 'Response Download': `${(bundleEntry.responseEnd - bundleEntry.responseStart).toFixed(2)} ms`, | |
| 'Total Duration': `${bundleEntry.duration.toFixed(2)} ms`, | |
| }; | |
| // transferSize of 0 often indicates 304 response | |
| if (bundleEntry.transferSize === 0) { | |
| info['Cache Status'] = '🟢 304 (or disk cache) - transferSize is 0'; | |
| } else { | |
| info['Cache Status'] = '🔴 200 (fresh download)'; | |
| } | |
| document.getElementById('resourceTiming').textContent = | |
| Object.entries(info).map(([k, v]) => `${k}: ${v}`).join('\n'); | |
| } else { | |
| document.getElementById('resourceTiming').textContent = 'Resource timing not available yet.'; | |
| } | |
| } | |
| // Initialize | |
| renderHistory(); | |
| // Dynamically load bundle.js with timing | |
| function loadBundleWithTiming() { | |
| const script = document.createElement('script'); | |
| script.src = '/bundle.js'; | |
| script.onload = () => { | |
| // Script has loaded and executed | |
| scriptLoadEnd = performance.now(); | |
| scriptExecuteEnd = performance.now(); | |
| // Small delay to ensure Resource Timing is populated | |
| setTimeout(() => { | |
| updateResults(); | |
| displayResourceTiming(); | |
| }, 50); | |
| }; | |
| script.onerror = () => { | |
| console.error('Failed to load bundle.js'); | |
| }; | |
| document.body.appendChild(script); | |
| } | |
| // Start loading after this script block | |
| loadBundleWithTiming(); | |
| </script> | |
| </body> | |
| </html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * V8 Code Cache Benchmark Bundle | |
| * | |
| * This file is designed to be large enough (>1KB) to benefit from V8's code caching. | |
| * It includes various utility functions that get compiled and cached. | |
| * | |
| * Size: ~30KB (minified would be smaller but we want readable code for testing) | |
| */ | |
| // ============================================================================ | |
| // PERFORMANCE MEASUREMENT | |
| // ============================================================================ | |
| const BenchmarkResults = { | |
| scriptLoadStart: performance.now(), | |
| compilationEnd: null, | |
| executionEnd: null, | |
| results: {}, | |
| }; | |
| // ============================================================================ | |
| // DATA STRUCTURES | |
| // ============================================================================ | |
| class PriorityQueue { | |
| constructor(comparator = (a, b) => a - b) { | |
| this.heap = []; | |
| this.comparator = comparator; | |
| } | |
| size() { | |
| return this.heap.length; | |
| } | |
| isEmpty() { | |
| return this.size() === 0; | |
| } | |
| peek() { | |
| return this.heap[0]; | |
| } | |
| push(value) { | |
| this.heap.push(value); | |
| this._siftUp(); | |
| return this.size(); | |
| } | |
| pop() { | |
| if (this.isEmpty()) return undefined; | |
| const top = this.heap[0]; | |
| const bottom = this.heap.pop(); | |
| if (!this.isEmpty()) { | |
| this.heap[0] = bottom; | |
| this._siftDown(); | |
| } | |
| return top; | |
| } | |
| _parent(i) { | |
| return ((i + 1) >>> 1) - 1; | |
| } | |
| _left(i) { | |
| return (i << 1) + 1; | |
| } | |
| _right(i) { | |
| return (i + 1) << 1; | |
| } | |
| _greater(i, j) { | |
| return this.comparator(this.heap[i], this.heap[j]) < 0; | |
| } | |
| _swap(i, j) { | |
| [this.heap[i], this.heap[j]] = [this.heap[j], this.heap[i]]; | |
| } | |
| _siftUp() { | |
| let node = this.size() - 1; | |
| while (node > 0 && this._greater(node, this._parent(node))) { | |
| this._swap(node, this._parent(node)); | |
| node = this._parent(node); | |
| } | |
| } | |
| _siftDown() { | |
| let node = 0; | |
| while ( | |
| (this._left(node) < this.size() && this._greater(this._left(node), node)) || | |
| (this._right(node) < this.size() && this._greater(this._right(node), node)) | |
| ) { | |
| const maxChild = | |
| this._right(node) < this.size() && this._greater(this._right(node), this._left(node)) | |
| ? this._right(node) | |
| : this._left(node); | |
| this._swap(node, maxChild); | |
| node = maxChild; | |
| } | |
| } | |
| } | |
| class LRUCache { | |
| constructor(capacity) { | |
| this.capacity = capacity; | |
| this.cache = new Map(); | |
| } | |
| get(key) { | |
| if (!this.cache.has(key)) return -1; | |
| const value = this.cache.get(key); | |
| this.cache.delete(key); | |
| this.cache.set(key, value); | |
| return value; | |
| } | |
| put(key, value) { | |
| if (this.cache.has(key)) { | |
| this.cache.delete(key); | |
| } else if (this.cache.size >= this.capacity) { | |
| const firstKey = this.cache.keys().next().value; | |
| this.cache.delete(firstKey); | |
| } | |
| this.cache.set(key, value); | |
| } | |
| } | |
| class Trie { | |
| constructor() { | |
| this.root = {}; | |
| } | |
| insert(word) { | |
| let node = this.root; | |
| for (const char of word) { | |
| if (!node[char]) node[char] = {}; | |
| node = node[char]; | |
| } | |
| node.isEnd = true; | |
| } | |
| search(word) { | |
| let node = this.root; | |
| for (const char of word) { | |
| if (!node[char]) return false; | |
| node = node[char]; | |
| } | |
| return node.isEnd === true; | |
| } | |
| startsWith(prefix) { | |
| let node = this.root; | |
| for (const char of prefix) { | |
| if (!node[char]) return false; | |
| node = node[char]; | |
| } | |
| return true; | |
| } | |
| } | |
| // ============================================================================ | |
| // ALGORITHMS | |
| // ============================================================================ | |
| function quickSort(arr, left = 0, right = arr.length - 1) { | |
| if (left < right) { | |
| const pivotIndex = partition(arr, left, right); | |
| quickSort(arr, left, pivotIndex - 1); | |
| quickSort(arr, pivotIndex + 1, right); | |
| } | |
| return arr; | |
| } | |
| function partition(arr, left, right) { | |
| const pivot = arr[right]; | |
| let i = left - 1; | |
| for (let j = left; j < right; j++) { | |
| if (arr[j] <= pivot) { | |
| i++; | |
| [arr[i], arr[j]] = [arr[j], arr[i]]; | |
| } | |
| } | |
| [arr[i + 1], arr[right]] = [arr[right], arr[i + 1]]; | |
| return i + 1; | |
| } | |
| function mergeSort(arr) { | |
| if (arr.length <= 1) return arr; | |
| const mid = Math.floor(arr.length / 2); | |
| const left = mergeSort(arr.slice(0, mid)); | |
| const right = mergeSort(arr.slice(mid)); | |
| return merge(left, right); | |
| } | |
| function merge(left, right) { | |
| const result = []; | |
| let i = 0, j = 0; | |
| while (i < left.length && j < right.length) { | |
| if (left[i] <= right[j]) { | |
| result.push(left[i++]); | |
| } else { | |
| result.push(right[j++]); | |
| } | |
| } | |
| return result.concat(left.slice(i)).concat(right.slice(j)); | |
| } | |
| function binarySearch(arr, target) { | |
| let left = 0, right = arr.length - 1; | |
| while (left <= right) { | |
| const mid = Math.floor((left + right) / 2); | |
| if (arr[mid] === target) return mid; | |
| if (arr[mid] < target) left = mid + 1; | |
| else right = mid - 1; | |
| } | |
| return -1; | |
| } | |
| function dijkstra(graph, start) { | |
| const distances = {}; | |
| const visited = new Set(); | |
| const pq = new PriorityQueue((a, b) => a.dist - b.dist); | |
| for (const vertex in graph) { | |
| distances[vertex] = Infinity; | |
| } | |
| distances[start] = 0; | |
| pq.push({ vertex: start, dist: 0 }); | |
| while (!pq.isEmpty()) { | |
| const { vertex, dist } = pq.pop(); | |
| if (visited.has(vertex)) continue; | |
| visited.add(vertex); | |
| for (const neighbor in graph[vertex]) { | |
| const newDist = dist + graph[vertex][neighbor]; | |
| if (newDist < distances[neighbor]) { | |
| distances[neighbor] = newDist; | |
| pq.push({ vertex: neighbor, dist: newDist }); | |
| } | |
| } | |
| } | |
| return distances; | |
| } | |
| function levenshteinDistance(str1, str2) { | |
| const m = str1.length; | |
| const n = str2.length; | |
| const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0)); | |
| for (let i = 0; i <= m; i++) dp[i][0] = i; | |
| for (let j = 0; j <= n; j++) dp[0][j] = j; | |
| for (let i = 1; i <= m; i++) { | |
| for (let j = 1; j <= n; j++) { | |
| if (str1[i - 1] === str2[j - 1]) { | |
| dp[i][j] = dp[i - 1][j - 1]; | |
| } else { | |
| dp[i][j] = 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]); | |
| } | |
| } | |
| } | |
| return dp[m][n]; | |
| } | |
| function longestCommonSubsequence(text1, text2) { | |
| const m = text1.length; | |
| const n = text2.length; | |
| const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0)); | |
| for (let i = 1; i <= m; i++) { | |
| for (let j = 1; j <= n; j++) { | |
| if (text1[i - 1] === text2[j - 1]) { | |
| dp[i][j] = dp[i - 1][j - 1] + 1; | |
| } else { | |
| dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); | |
| } | |
| } | |
| } | |
| return dp[m][n]; | |
| } | |
| function knapsack(weights, values, capacity) { | |
| const n = weights.length; | |
| const dp = Array(n + 1).fill(null).map(() => Array(capacity + 1).fill(0)); | |
| for (let i = 1; i <= n; i++) { | |
| for (let w = 0; w <= capacity; w++) { | |
| if (weights[i - 1] <= w) { | |
| dp[i][w] = Math.max( | |
| dp[i - 1][w], | |
| dp[i - 1][w - weights[i - 1]] + values[i - 1] | |
| ); | |
| } else { | |
| dp[i][w] = dp[i - 1][w]; | |
| } | |
| } | |
| } | |
| return dp[n][capacity]; | |
| } | |
| // ============================================================================ | |
| // STRING UTILITIES | |
| // ============================================================================ | |
| function isPalindrome(str) { | |
| const cleaned = str.toLowerCase().replace(/[^a-z0-9]/g, ''); | |
| let left = 0, right = cleaned.length - 1; | |
| while (left < right) { | |
| if (cleaned[left++] !== cleaned[right--]) return false; | |
| } | |
| return true; | |
| } | |
| function longestPalindromicSubstring(s) { | |
| if (s.length < 2) return s; | |
| let start = 0, maxLen = 1; | |
| function expandAroundCenter(left, right) { | |
| while (left >= 0 && right < s.length && s[left] === s[right]) { | |
| const len = right - left + 1; | |
| if (len > maxLen) { | |
| start = left; | |
| maxLen = len; | |
| } | |
| left--; | |
| right++; | |
| } | |
| } | |
| for (let i = 0; i < s.length; i++) { | |
| expandAroundCenter(i, i); | |
| expandAroundCenter(i, i + 1); | |
| } | |
| return s.substring(start, start + maxLen); | |
| } | |
| function kmpSearch(text, pattern) { | |
| const lps = computeLPS(pattern); | |
| const result = []; | |
| let i = 0, j = 0; | |
| while (i < text.length) { | |
| if (pattern[j] === text[i]) { | |
| i++; | |
| j++; | |
| } | |
| if (j === pattern.length) { | |
| result.push(i - j); | |
| j = lps[j - 1]; | |
| } else if (i < text.length && pattern[j] !== text[i]) { | |
| if (j !== 0) { | |
| j = lps[j - 1]; | |
| } else { | |
| i++; | |
| } | |
| } | |
| } | |
| return result; | |
| } | |
| function computeLPS(pattern) { | |
| const lps = [0]; | |
| let len = 0; | |
| let i = 1; | |
| while (i < pattern.length) { | |
| if (pattern[i] === pattern[len]) { | |
| len++; | |
| lps[i] = len; | |
| i++; | |
| } else { | |
| if (len !== 0) { | |
| len = lps[len - 1]; | |
| } else { | |
| lps[i] = 0; | |
| i++; | |
| } | |
| } | |
| } | |
| return lps; | |
| } | |
| function rabin_karp(text, pattern) { | |
| const d = 256; | |
| const q = 101; | |
| const m = pattern.length; | |
| const n = text.length; | |
| const result = []; | |
| let h = 1; | |
| for (let i = 0; i < m - 1; i++) { | |
| h = (h * d) % q; | |
| } | |
| let p = 0, t = 0; | |
| for (let i = 0; i < m; i++) { | |
| p = (d * p + pattern.charCodeAt(i)) % q; | |
| t = (d * t + text.charCodeAt(i)) % q; | |
| } | |
| for (let i = 0; i <= n - m; i++) { | |
| if (p === t) { | |
| let match = true; | |
| for (let j = 0; j < m; j++) { | |
| if (text[i + j] !== pattern[j]) { | |
| match = false; | |
| break; | |
| } | |
| } | |
| if (match) result.push(i); | |
| } | |
| if (i < n - m) { | |
| t = (d * (t - text.charCodeAt(i) * h) + text.charCodeAt(i + m)) % q; | |
| if (t < 0) t += q; | |
| } | |
| } | |
| return result; | |
| } | |
| // ============================================================================ | |
| // MATH UTILITIES | |
| // ============================================================================ | |
| function fibonacci(n) { | |
| if (n <= 1) return n; | |
| let a = 0, b = 1; | |
| for (let i = 2; i <= n; i++) { | |
| [a, b] = [b, a + b]; | |
| } | |
| return b; | |
| } | |
| function factorial(n) { | |
| if (n <= 1) return 1n; | |
| let result = 1n; | |
| for (let i = 2n; i <= BigInt(n); i++) { | |
| result *= i; | |
| } | |
| return result; | |
| } | |
| function gcd(a, b) { | |
| while (b !== 0) { | |
| [a, b] = [b, a % b]; | |
| } | |
| return a; | |
| } | |
| function lcm(a, b) { | |
| return (a * b) / gcd(a, b); | |
| } | |
| function isPrime(n) { | |
| if (n < 2) return false; | |
| if (n === 2) return true; | |
| if (n % 2 === 0) return false; | |
| for (let i = 3; i <= Math.sqrt(n); i += 2) { | |
| if (n % i === 0) return false; | |
| } | |
| return true; | |
| } | |
| function sieveOfEratosthenes(limit) { | |
| const primes = []; | |
| const sieve = new Array(limit + 1).fill(true); | |
| sieve[0] = sieve[1] = false; | |
| for (let i = 2; i <= limit; i++) { | |
| if (sieve[i]) { | |
| primes.push(i); | |
| for (let j = i * i; j <= limit; j += i) { | |
| sieve[j] = false; | |
| } | |
| } | |
| } | |
| return primes; | |
| } | |
| function matrixMultiply(A, B) { | |
| const rowsA = A.length; | |
| const colsA = A[0].length; | |
| const colsB = B[0].length; | |
| const result = Array(rowsA).fill(null).map(() => Array(colsB).fill(0)); | |
| for (let i = 0; i < rowsA; i++) { | |
| for (let j = 0; j < colsB; j++) { | |
| for (let k = 0; k < colsA; k++) { | |
| result[i][j] += A[i][k] * B[k][j]; | |
| } | |
| } | |
| } | |
| return result; | |
| } | |
| function determinant(matrix) { | |
| const n = matrix.length; | |
| if (n === 1) return matrix[0][0]; | |
| if (n === 2) return matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0]; | |
| let det = 0; | |
| for (let j = 0; j < n; j++) { | |
| const submatrix = []; | |
| for (let i = 1; i < n; i++) { | |
| submatrix.push([...matrix[i].slice(0, j), ...matrix[i].slice(j + 1)]); | |
| } | |
| det += Math.pow(-1, j) * matrix[0][j] * determinant(submatrix); | |
| } | |
| return det; | |
| } | |
| // ============================================================================ | |
| // ARRAY UTILITIES | |
| // ============================================================================ | |
| function shuffle(array) { | |
| const result = [...array]; | |
| for (let i = result.length - 1; i > 0; i--) { | |
| const j = Math.floor(Math.random() * (i + 1)); | |
| [result[i], result[j]] = [result[j], result[i]]; | |
| } | |
| return result; | |
| } | |
| function chunk(array, size) { | |
| const result = []; | |
| for (let i = 0; i < array.length; i += size) { | |
| result.push(array.slice(i, i + size)); | |
| } | |
| return result; | |
| } | |
| function flatten(array, depth = 1) { | |
| if (depth < 1) return array.slice(); | |
| return array.reduce((acc, val) => | |
| acc.concat(Array.isArray(val) ? flatten(val, depth - 1) : val), | |
| []); | |
| } | |
| function unique(array) { | |
| return [...new Set(array)]; | |
| } | |
| function groupBy(array, key) { | |
| return array.reduce((result, item) => { | |
| const group = typeof key === 'function' ? key(item) : item[key]; | |
| (result[group] = result[group] || []).push(item); | |
| return result; | |
| }, {}); | |
| } | |
| function difference(arr1, arr2) { | |
| const set = new Set(arr2); | |
| return arr1.filter(item => !set.has(item)); | |
| } | |
| function intersection(arr1, arr2) { | |
| const set = new Set(arr2); | |
| return arr1.filter(item => set.has(item)); | |
| } | |
| function union(arr1, arr2) { | |
| return [...new Set([...arr1, ...arr2])]; | |
| } | |
| // ============================================================================ | |
| // OBJECT UTILITIES | |
| // ============================================================================ | |
| function deepClone(obj) { | |
| if (obj === null || typeof obj !== 'object') return obj; | |
| if (Array.isArray(obj)) return obj.map(deepClone); | |
| const cloned = {}; | |
| for (const key in obj) { | |
| if (obj.hasOwnProperty(key)) { | |
| cloned[key] = deepClone(obj[key]); | |
| } | |
| } | |
| return cloned; | |
| } | |
| function deepMerge(target, ...sources) { | |
| if (!sources.length) return target; | |
| const source = sources.shift(); | |
| if (isObject(target) && isObject(source)) { | |
| for (const key in source) { | |
| if (isObject(source[key])) { | |
| if (!target[key]) Object.assign(target, { [key]: {} }); | |
| deepMerge(target[key], source[key]); | |
| } else { | |
| Object.assign(target, { [key]: source[key] }); | |
| } | |
| } | |
| } | |
| return deepMerge(target, ...sources); | |
| } | |
| function isObject(item) { | |
| return item && typeof item === 'object' && !Array.isArray(item); | |
| } | |
| function pick(obj, keys) { | |
| return keys.reduce((result, key) => { | |
| if (key in obj) result[key] = obj[key]; | |
| return result; | |
| }, {}); | |
| } | |
| function omit(obj, keys) { | |
| const keySet = new Set(keys); | |
| return Object.keys(obj).reduce((result, key) => { | |
| if (!keySet.has(key)) result[key] = obj[key]; | |
| return result; | |
| }, {}); | |
| } | |
| function get(obj, path, defaultValue = undefined) { | |
| const keys = path.split('.'); | |
| let result = obj; | |
| for (const key of keys) { | |
| if (result === null || result === undefined) return defaultValue; | |
| result = result[key]; | |
| } | |
| return result === undefined ? defaultValue : result; | |
| } | |
| function set(obj, path, value) { | |
| const keys = path.split('.'); | |
| let current = obj; | |
| for (let i = 0; i < keys.length - 1; i++) { | |
| const key = keys[i]; | |
| if (!(key in current) || typeof current[key] !== 'object') { | |
| current[key] = {}; | |
| } | |
| current = current[key]; | |
| } | |
| current[keys[keys.length - 1]] = value; | |
| return obj; | |
| } | |
| // ============================================================================ | |
| // ASYNC UTILITIES | |
| // ============================================================================ | |
| function debounce(func, wait) { | |
| let timeout; | |
| return function executedFunction(...args) { | |
| const later = () => { | |
| clearTimeout(timeout); | |
| func(...args); | |
| }; | |
| clearTimeout(timeout); | |
| timeout = setTimeout(later, wait); | |
| }; | |
| } | |
| function throttle(func, limit) { | |
| let inThrottle; | |
| return function executedFunction(...args) { | |
| if (!inThrottle) { | |
| func(...args); | |
| inThrottle = true; | |
| setTimeout(() => (inThrottle = false), limit); | |
| } | |
| }; | |
| } | |
| function sleep(ms) { | |
| return new Promise(resolve => setTimeout(resolve, ms)); | |
| } | |
| async function retry(fn, retries = 3, delay = 1000) { | |
| for (let i = 0; i < retries; i++) { | |
| try { | |
| return await fn(); | |
| } catch (error) { | |
| if (i === retries - 1) throw error; | |
| await sleep(delay); | |
| } | |
| } | |
| } | |
| function memoize(fn) { | |
| const cache = new Map(); | |
| return function(...args) { | |
| const key = JSON.stringify(args); | |
| if (cache.has(key)) return cache.get(key); | |
| const result = fn.apply(this, args); | |
| cache.set(key, result); | |
| return result; | |
| }; | |
| } | |
| // ============================================================================ | |
| // BENCHMARK EXECUTION | |
| // ============================================================================ | |
| function runBenchmark() { | |
| const startTime = performance.now(); | |
| // Test sorting algorithms | |
| const testArray = Array.from({ length: 1000 }, () => Math.floor(Math.random() * 10000)); | |
| quickSort([...testArray]); | |
| mergeSort([...testArray]); | |
| // Test data structures | |
| const pq = new PriorityQueue(); | |
| for (let i = 0; i < 500; i++) pq.push(Math.random()); | |
| while (!pq.isEmpty()) pq.pop(); | |
| const cache = new LRUCache(100); | |
| for (let i = 0; i < 500; i++) cache.put(i, `value${i}`); | |
| for (let i = 0; i < 500; i++) cache.get(i % 150); | |
| const trie = new Trie(); | |
| const words = ['hello', 'world', 'javascript', 'benchmark', 'performance', 'optimization']; | |
| words.forEach(w => trie.insert(w)); | |
| words.forEach(w => trie.search(w)); | |
| // Test string algorithms | |
| isPalindrome('A man a plan a canal Panama'); | |
| longestPalindromicSubstring('babad'); | |
| levenshteinDistance('kitten', 'sitting'); | |
| longestCommonSubsequence('abcde', 'ace'); | |
| kmpSearch('ABABDABACDABABCABAB', 'ABABCABAB'); | |
| // Test math functions | |
| fibonacci(40); | |
| sieveOfEratosthenes(1000); | |
| const matrix1 = [[1, 2], [3, 4]]; | |
| const matrix2 = [[5, 6], [7, 8]]; | |
| matrixMultiply(matrix1, matrix2); | |
| determinant([[1, 2, 3], [4, 5, 6], [7, 8, 9]]); | |
| // Test array utilities | |
| const arr = Array.from({ length: 100 }, (_, i) => i); | |
| shuffle(arr); | |
| chunk(arr, 10); | |
| groupBy([{ age: 20 }, { age: 25 }, { age: 20 }], 'age'); | |
| // Test object utilities | |
| const obj = { a: { b: { c: 1 } } }; | |
| deepClone(obj); | |
| get(obj, 'a.b.c'); | |
| // Test dynamic programming | |
| knapsack([10, 20, 30], [60, 100, 120], 50); | |
| const endTime = performance.now(); | |
| return endTime - startTime; | |
| } | |
| // Record compilation end time | |
| BenchmarkResults.compilationEnd = performance.now(); | |
| // Run the benchmark | |
| const executionTime = runBenchmark(); | |
| BenchmarkResults.executionEnd = performance.now(); | |
| // Store results | |
| BenchmarkResults.results = { | |
| compilationTime: BenchmarkResults.compilationEnd - BenchmarkResults.scriptLoadStart, | |
| executionTime: executionTime, | |
| totalTime: BenchmarkResults.executionEnd - BenchmarkResults.scriptLoadStart, | |
| }; | |
| // Export for external access | |
| window.BenchmarkResults = BenchmarkResults; | |
| window.BenchmarkLibrary = { | |
| PriorityQueue, | |
| LRUCache, | |
| Trie, | |
| quickSort, | |
| mergeSort, | |
| binarySearch, | |
| dijkstra, | |
| levenshteinDistance, | |
| longestCommonSubsequence, | |
| knapsack, | |
| isPalindrome, | |
| longestPalindromicSubstring, | |
| kmpSearch, | |
| rabin_karp, | |
| fibonacci, | |
| factorial, | |
| gcd, | |
| lcm, | |
| isPrime, | |
| sieveOfEratosthenes, | |
| matrixMultiply, | |
| determinant, | |
| shuffle, | |
| chunk, | |
| flatten, | |
| unique, | |
| groupBy, | |
| difference, | |
| intersection, | |
| union, | |
| deepClone, | |
| deepMerge, | |
| pick, | |
| omit, | |
| get, | |
| set, | |
| debounce, | |
| throttle, | |
| sleep, | |
| retry, | |
| memoize, | |
| runBenchmark, | |
| }; | |
| console.log('[Bundle] Loaded and executed:', BenchmarkResults.results); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "name": "v8-code-cache-benchmark", | |
| "version": "1.0.0", | |
| "description": "Benchmark to validate V8 code caching behavior with HTTP 304 vs 200 responses", | |
| "type": "module", | |
| "scripts": { | |
| "start": "node server.js", | |
| "start:force-200": "node server.js --force-200", | |
| "benchmark": "node benchmark-runner.js", | |
| "benchmark:trace": "node benchmark-with-tracing.js", | |
| "test": "echo 'Start server with npm start, then open http://localhost:3000'" | |
| }, | |
| "keywords": [ | |
| "v8", | |
| "code-caching", | |
| "bytecode-cache", | |
| "performance", | |
| "benchmark", | |
| "http-304" | |
| ], | |
| "author": "", | |
| "license": "MIT", | |
| "optionalDependencies": { | |
| "puppeteer": "^21.0.0" | |
| }, | |
| "engines": { | |
| "node": ">=18.0.0" | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * V8 Code Cache Benchmark Server | |
| * | |
| * This server helps demonstrate the difference between: | |
| * - 304 Not Modified (preserves V8 code cache) | |
| * - 200 OK (clears V8 code cache, even with same content) | |
| * | |
| * Usage: | |
| * node server.js [--force-200] [--port=3000] | |
| * | |
| * Options: | |
| * --force-200 Always return 200 even when ETag matches (simulates deployment) | |
| * --port=XXXX Set the port (default: 3000) | |
| */ | |
| import http from 'http'; | |
| import fs from 'fs'; | |
| import path from 'path'; | |
| import crypto from 'crypto'; | |
| import { fileURLToPath } from 'url'; | |
| const __dirname = path.dirname(fileURLToPath(import.meta.url)); | |
| // Parse command line arguments | |
| const args = process.argv.slice(2); | |
| const FORCE_200 = args.includes('--force-200'); | |
| const PORT = parseInt(args.find(a => a.startsWith('--port='))?.split('=')[1] || '3000'); | |
| // Generate ETag from file content | |
| function generateETag(content) { | |
| return `"${crypto.createHash('md5').update(content).digest('hex')}"`; | |
| } | |
| // MIME types | |
| const MIME_TYPES = { | |
| '.html': 'text/html', | |
| '.js': 'application/javascript', | |
| '.css': 'text/css', | |
| '.json': 'application/json', | |
| }; | |
| // Request counter for logging | |
| let requestCount = 0; | |
| const server = http.createServer((req, res) => { | |
| requestCount++; | |
| const reqNum = requestCount; | |
| const timestamp = new Date().toISOString(); | |
| console.log(`\n[${reqNum}] ${timestamp} - ${req.method} ${req.url}`); | |
| console.log(` If-None-Match: ${req.headers['if-none-match'] || '(none)'}`); | |
| console.log(` If-Modified-Since: ${req.headers['if-modified-since'] || '(none)'}`); | |
| // Handle CORS preflight | |
| if (req.method === 'OPTIONS') { | |
| res.writeHead(204, { | |
| 'Access-Control-Allow-Origin': '*', | |
| 'Access-Control-Allow-Methods': 'GET, OPTIONS', | |
| 'Access-Control-Allow-Headers': 'Content-Type', | |
| }); | |
| res.end(); | |
| return; | |
| } | |
| // Parse URL | |
| let urlPath = req.url.split('?')[0]; | |
| if (urlPath === '/') urlPath = '/index.html'; | |
| const filePath = path.join(__dirname, 'public', urlPath); | |
| const ext = path.extname(filePath); | |
| // Check if file exists | |
| if (!fs.existsSync(filePath)) { | |
| console.log(` Response: 404 Not Found`); | |
| res.writeHead(404, { 'Content-Type': 'text/plain' }); | |
| res.end('Not Found'); | |
| return; | |
| } | |
| // Read file | |
| const content = fs.readFileSync(filePath); | |
| const etag = generateETag(content); | |
| const lastModified = fs.statSync(filePath).mtime.toUTCString(); | |
| // Check for conditional request | |
| const clientETag = req.headers['if-none-match']; | |
| const clientLastModified = req.headers['if-modified-since']; | |
| const etagMatch = clientETag === etag; | |
| const lastModifiedMatch = clientLastModified === lastModified; | |
| // Determine response | |
| if ((etagMatch || lastModifiedMatch) && !FORCE_200) { | |
| // Return 304 - This preserves V8 code cache! | |
| console.log(` ETag match: ${etagMatch}, Last-Modified match: ${lastModifiedMatch}`); | |
| console.log(` Response: 304 Not Modified (V8 code cache PRESERVED)`); | |
| res.writeHead(304, { | |
| 'ETag': etag, | |
| 'Last-Modified': lastModified, | |
| 'Cache-Control': 'max-age=0, must-revalidate', | |
| 'Access-Control-Allow-Origin': '*', | |
| }); | |
| res.end(); | |
| } else { | |
| // Return 200 - This clears V8 code cache! | |
| const reason = FORCE_200 ? 'FORCE_200 mode' : 'No cache headers / cache miss'; | |
| console.log(` Response: 200 OK (${reason}) - V8 code cache CLEARED`); | |
| res.writeHead(200, { | |
| 'Content-Type': MIME_TYPES[ext] || 'application/octet-stream', | |
| 'Content-Length': content.length, | |
| 'ETag': etag, | |
| 'Last-Modified': lastModified, | |
| 'Cache-Control': 'max-age=0, must-revalidate', | |
| 'Access-Control-Allow-Origin': '*', | |
| }); | |
| res.end(content); | |
| } | |
| }); | |
| server.listen(PORT, () => { | |
| console.log('='.repeat(60)); | |
| console.log('V8 Code Cache Benchmark Server'); | |
| console.log('='.repeat(60)); | |
| console.log(`Mode: ${FORCE_200 ? 'FORCE 200 (simulates deployment)' : 'NORMAL (supports 304)'}`); | |
| console.log(`Port: ${PORT}`); | |
| console.log(`URL: http://localhost:${PORT}`); | |
| console.log('='.repeat(60)); | |
| console.log('\nWaiting for requests...\n'); | |
| }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment