Skip to content

Instantly share code, notes, and snippets.

@edwardkenfox
Created December 26, 2025 05:30
Show Gist options
  • Select an option

  • Save edwardkenfox/2b1a4315486551ea4d96803fbe92eb3a to your computer and use it in GitHub Desktop.

Select an option

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
/**
* 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);
<!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>
/**
* 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);
{
"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"
}
}
/**
* 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