mirror of
https://github.com/hanzoai/react.git
synced 2026-09-24 17:02:27 +00:00
Things I found while integrating this bench into https://github.com/vercel/next.js/pull/96234. See code. --------- Co-authored-by: Hanzo Dev <[email protected]>
793 lines
21 KiB
JavaScript
793 lines
21 KiB
JavaScript
'use strict';
|
|
|
|
require('@babel/register')({
|
|
presets: [['@babel/preset-react', {runtime: 'automatic'}]],
|
|
plugins: ['@babel/plugin-transform-modules-commonjs'],
|
|
only: [/\/src\//],
|
|
});
|
|
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
const webpack = require('webpack');
|
|
const inspector = require('node:inspector');
|
|
|
|
const {clientManifest, ssrManifest} = require('./webpack-mock');
|
|
|
|
const PROFILE_MODE = process.argv.includes('--profile');
|
|
const CONCURRENT_MODE = process.argv.includes('--concurrent');
|
|
const INJECT = !process.argv.includes('--no-injection');
|
|
const JSON_OUT = (function () {
|
|
const arg = process.argv.find(function (a) {
|
|
return a.startsWith('--json-out=');
|
|
});
|
|
return arg ? arg.slice('--json-out='.length) : null;
|
|
})();
|
|
const jsonResults = [];
|
|
function writeJsonOut(mode) {
|
|
if (!JSON_OUT) return;
|
|
fs.writeFileSync(JSON_OUT, JSON.stringify({mode, results: jsonResults}));
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Build
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function build() {
|
|
const config = require('./webpack.config');
|
|
return new Promise(function (resolve, reject) {
|
|
webpack(config, function (err, stats) {
|
|
if (err) {
|
|
reject(err);
|
|
return;
|
|
}
|
|
if (stats.hasErrors()) {
|
|
reject(new Error(stats.toString({errors: true})));
|
|
return;
|
|
}
|
|
console.log(
|
|
stats.toString({colors: true, modules: false, entrypoints: false})
|
|
);
|
|
resolve();
|
|
});
|
|
});
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Render helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const {
|
|
renderFizzNode: renderFizzNodeStream,
|
|
renderFizzEdge: renderFizzEdgeStream,
|
|
renderFlightFizzNode: renderFlightFizzNodeStream,
|
|
renderFlightFizzEdge: renderFlightFizzEdgeStream,
|
|
nodeStreamToString,
|
|
webStreamToString,
|
|
} = require('./render-helpers');
|
|
const {printGrid} = require('./print-helpers');
|
|
|
|
function renderFizzNode(AppComponent, itemCount) {
|
|
return nodeStreamToString(renderFizzNodeStream(AppComponent, itemCount));
|
|
}
|
|
|
|
function renderFizzEdge(AppComponent, itemCount) {
|
|
return renderFizzEdgeStream(AppComponent, itemCount).then(webStreamToString);
|
|
}
|
|
|
|
function renderFlightFizzNode(renderRSCNode, AppComponent, itemCount) {
|
|
return nodeStreamToString(
|
|
renderFlightFizzNodeStream(
|
|
renderRSCNode,
|
|
AppComponent,
|
|
itemCount,
|
|
clientManifest,
|
|
ssrManifest,
|
|
{inject: INJECT}
|
|
)
|
|
);
|
|
}
|
|
|
|
function renderFlightFizzEdge(renderRSCEdge, AppComponent, itemCount) {
|
|
return renderFlightFizzEdgeStream(
|
|
renderRSCEdge,
|
|
AppComponent,
|
|
itemCount,
|
|
clientManifest,
|
|
ssrManifest,
|
|
{inject: INJECT}
|
|
).then(webStreamToString);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Benchmarking
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const canGC = typeof globalThis.gc === 'function';
|
|
|
|
// Yield to the event loop's check phase between renders. React schedules a
|
|
// setImmediate per request, but a fully in-process render completes entirely
|
|
// in microtasks/nextTicks, so a tight benchmark loop never lets those
|
|
// immediates run. They then pile up in Node's immediate queue, each one
|
|
// retaining its (otherwise finished) request graph, which inflates any memory
|
|
// measurement. A real server yields to the event loop on every request, so
|
|
// draining between iterations is both correct and more representative.
|
|
function tick() {
|
|
return new Promise(resolve => setImmediate(resolve));
|
|
}
|
|
|
|
async function settledHeapUsed() {
|
|
await tick();
|
|
if (canGC) globalThis.gc();
|
|
return process.memoryUsage().heapUsed;
|
|
}
|
|
|
|
async function runBenchmark(name, fn, iterations, warmup) {
|
|
if (canGC) globalThis.gc();
|
|
const heapBefore = await settledHeapUsed();
|
|
|
|
// Warmup
|
|
for (let i = 0; i < warmup; i++) {
|
|
await fn();
|
|
await tick();
|
|
}
|
|
|
|
// Collect GC pauses during timed iterations.
|
|
let gcCount = 0;
|
|
let gcTotalMs = 0;
|
|
const gcObs = new PerformanceObserver(list => {
|
|
for (const entry of list.getEntries()) {
|
|
gcCount++;
|
|
gcTotalMs += entry.duration;
|
|
}
|
|
});
|
|
gcObs.observe({entryTypes: ['gc']});
|
|
|
|
// Timed iterations. The tick between iterations is excluded from the
|
|
// timed window.
|
|
const times = [];
|
|
for (let i = 0; i < iterations; i++) {
|
|
const start = performance.now();
|
|
await fn();
|
|
times.push(performance.now() - start);
|
|
await tick();
|
|
}
|
|
gcObs.disconnect();
|
|
const heapAfter = await settledHeapUsed();
|
|
|
|
// Trim top/bottom 5% to remove outliers
|
|
const sorted = [...times].sort((a, b) => a - b);
|
|
const trimCount = Math.floor(sorted.length * 0.05);
|
|
const trimmed = sorted.slice(trimCount, sorted.length - trimCount);
|
|
|
|
const mean = trimmed.reduce((s, t) => s + t, 0) / trimmed.length;
|
|
const median = sorted[Math.floor(sorted.length / 2)];
|
|
const stddev = Math.sqrt(
|
|
trimmed.reduce((s, t) => s + (t - mean) ** 2, 0) / trimmed.length
|
|
);
|
|
const p95 = sorted[Math.floor(sorted.length * 0.95)];
|
|
const min = sorted[0];
|
|
const max = sorted[sorted.length - 1];
|
|
|
|
return {
|
|
name,
|
|
mean,
|
|
median,
|
|
stddev,
|
|
p95,
|
|
min,
|
|
max,
|
|
iterations,
|
|
gcCount,
|
|
gcTotalMs,
|
|
heapBefore,
|
|
heapAfter,
|
|
};
|
|
}
|
|
|
|
function printResult(result) {
|
|
jsonResults.push(result);
|
|
console.log(' %s:', result.name);
|
|
console.log(' Mean: %s ms', result.mean.toFixed(2));
|
|
console.log(' Median: %s ms', result.median.toFixed(2));
|
|
console.log(' Stddev: %s ms', result.stddev.toFixed(2));
|
|
console.log(' P95: %s ms', result.p95.toFixed(2));
|
|
console.log(' Min: %s ms', result.min.toFixed(2));
|
|
console.log(' Max: %s ms', result.max.toFixed(2));
|
|
console.log(
|
|
' GC: %d pauses, %s ms total (%s ms/iter)',
|
|
result.gcCount,
|
|
result.gcTotalMs.toFixed(1),
|
|
(result.gcTotalMs / result.iterations).toFixed(2)
|
|
);
|
|
printHeap(result);
|
|
}
|
|
|
|
function printHeap(result) {
|
|
if (!canGC) return;
|
|
const mb = b => (b / 1048576).toFixed(1);
|
|
const delta = result.heapAfter - result.heapBefore;
|
|
console.log(
|
|
' Heap: %s MB retained after run (%s%s MB vs before)',
|
|
mb(result.heapAfter),
|
|
delta >= 0 ? '+' : '',
|
|
mb(delta)
|
|
);
|
|
}
|
|
|
|
async function runConcurrent(name, fn, total, concurrency, warmup) {
|
|
if (canGC) globalThis.gc();
|
|
const heapBefore = await settledHeapUsed();
|
|
|
|
for (let i = 0; i < warmup; i++) {
|
|
await fn();
|
|
await tick();
|
|
}
|
|
|
|
let gcCount = 0;
|
|
let gcTotalMs = 0;
|
|
const gcObs = new PerformanceObserver(list => {
|
|
for (const entry of list.getEntries()) {
|
|
gcCount++;
|
|
gcTotalMs += entry.duration;
|
|
}
|
|
});
|
|
gcObs.observe({entryTypes: ['gc']});
|
|
|
|
const latencies = new Array(total);
|
|
let completed = 0;
|
|
let launched = 0;
|
|
|
|
const start = performance.now();
|
|
await new Promise(resolve => {
|
|
function launch() {
|
|
while (launched < total && launched - completed < concurrency) {
|
|
const idx = launched++;
|
|
const t0 = performance.now();
|
|
fn()
|
|
.then(() => {
|
|
latencies[idx] = performance.now() - t0;
|
|
// Drain immediates before freeing the slot (see tick()).
|
|
return tick();
|
|
})
|
|
.then(() => {
|
|
completed++;
|
|
if (completed === total) {
|
|
resolve();
|
|
} else {
|
|
launch();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
launch();
|
|
});
|
|
const elapsed = performance.now() - start;
|
|
gcObs.disconnect();
|
|
const heapAfter = await settledHeapUsed();
|
|
|
|
const sorted = [...latencies].sort((a, b) => a - b);
|
|
const mean = sorted.reduce((s, t) => s + t, 0) / sorted.length;
|
|
const p95 = sorted[Math.floor(sorted.length * 0.95)];
|
|
|
|
return {
|
|
name,
|
|
reqPerSec: (total / elapsed) * 1000,
|
|
mean,
|
|
p95,
|
|
total,
|
|
concurrency,
|
|
gcCount,
|
|
gcTotalMs,
|
|
heapBefore,
|
|
heapAfter,
|
|
};
|
|
}
|
|
|
|
function printConcurrentResult(result) {
|
|
jsonResults.push(result);
|
|
console.log(' %s:', result.name);
|
|
console.log(' Req/s: %s', result.reqPerSec.toFixed(1));
|
|
console.log(' Mean: %s ms', result.mean.toFixed(2));
|
|
console.log(' P95: %s ms', result.p95.toFixed(2));
|
|
console.log(
|
|
' GC: %d pauses, %s ms total (%s ms/req)',
|
|
result.gcCount,
|
|
result.gcTotalMs.toFixed(1),
|
|
(result.gcTotalMs / result.total).toFixed(2)
|
|
);
|
|
printHeap(result);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// CPU Profiling
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function startProfiler() {
|
|
const session = new inspector.Session();
|
|
session.connect();
|
|
return new Promise(function (resolve, reject) {
|
|
session.post('Profiler.enable', function (err) {
|
|
if (err) {
|
|
reject(err);
|
|
return;
|
|
}
|
|
session.post('Profiler.start', function (err2) {
|
|
if (err2) {
|
|
reject(err2);
|
|
return;
|
|
}
|
|
resolve(session);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
function stopProfiler(session, outputPath) {
|
|
return new Promise(function (resolve, reject) {
|
|
session.post('Profiler.stop', function (err, {profile}) {
|
|
if (err) {
|
|
reject(err);
|
|
return;
|
|
}
|
|
fs.mkdirSync(path.dirname(outputPath), {recursive: true});
|
|
fs.writeFileSync(outputPath, JSON.stringify(profile));
|
|
session.post('Profiler.disable');
|
|
session.disconnect();
|
|
resolve(profile);
|
|
});
|
|
});
|
|
}
|
|
|
|
function printTopFunctions(profile, topN) {
|
|
// Aggregate self-time per function from the profile nodes.
|
|
const selfTimes = new Map();
|
|
for (const node of profile.nodes) {
|
|
const name = node.callFrame.functionName || '(anonymous)';
|
|
const loc = node.callFrame.url
|
|
? node.callFrame.url.replace(/.*\//, '') + ':' + node.callFrame.lineNumber
|
|
: '(native)';
|
|
const key = name + ' @ ' + loc;
|
|
const hitCount = node.hitCount || 0;
|
|
selfTimes.set(key, (selfTimes.get(key) || 0) + hitCount);
|
|
}
|
|
|
|
const sorted = [...selfTimes.entries()]
|
|
.sort((a, b) => b[1] - a[1])
|
|
.slice(0, topN);
|
|
|
|
const totalSamples = profile.nodes.reduce((s, n) => s + (n.hitCount || 0), 0);
|
|
|
|
console.log(' Top %d functions by self-time:', topN);
|
|
for (const [key, hits] of sorted) {
|
|
const pct = ((hits / totalSamples) * 100).toFixed(1);
|
|
console.log(' %s%% - %s', pct, key);
|
|
}
|
|
}
|
|
|
|
async function profileRun(name, fn, warmup, iterations, outputPath) {
|
|
// Warmup (unprofiled)
|
|
for (let i = 0; i < warmup; i++) {
|
|
await fn();
|
|
await tick();
|
|
}
|
|
|
|
// Collect GC pauses during the profiled run.
|
|
let gcCount = 0;
|
|
let gcTotalMs = 0;
|
|
const gcObs = new PerformanceObserver(list => {
|
|
for (const entry of list.getEntries()) {
|
|
gcCount++;
|
|
gcTotalMs += entry.duration;
|
|
}
|
|
});
|
|
gcObs.observe({entryTypes: ['gc']});
|
|
|
|
// Profiled run
|
|
const session = await startProfiler();
|
|
for (let i = 0; i < iterations; i++) {
|
|
await fn();
|
|
await tick();
|
|
}
|
|
const profile = await stopProfiler(session, outputPath);
|
|
gcObs.disconnect();
|
|
|
|
console.log(' %s → %s', name, outputPath);
|
|
printTopFunctions(profile, 10);
|
|
console.log(
|
|
' GC: %d pauses, %s ms total (%s ms/iter)',
|
|
gcCount,
|
|
gcTotalMs.toFixed(1),
|
|
(gcTotalMs / iterations).toFixed(2)
|
|
);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Main
|
|
// ---------------------------------------------------------------------------
|
|
|
|
async function main() {
|
|
console.log('Building RSC bundle...\n');
|
|
await build();
|
|
|
|
const {
|
|
renderRSCNode,
|
|
renderRSCEdge,
|
|
App: RSCApp,
|
|
AppAsync: RSCAppAsync,
|
|
} = require('./build/rsc-bundle.js');
|
|
const App = require('./src/App.js').default;
|
|
const AppAsync = require('./src/AppAsync.js').default;
|
|
|
|
const ITEM_COUNT = 200;
|
|
|
|
const WARMUP = 50;
|
|
const ITERATIONS = 1000;
|
|
const PROFILE_WARMUP = 50;
|
|
const PROFILE_ITERATIONS = 500;
|
|
|
|
// --- Verify renders ---
|
|
console.log('\n--- Verifying renders ---\n');
|
|
|
|
const fizzNodeHtml = await renderFizzNode(App, ITEM_COUNT);
|
|
console.log('Fizz (Node, sync): %d bytes', fizzNodeHtml.length);
|
|
|
|
const flightFizzNodeHtml = await renderFlightFizzNode(
|
|
renderRSCNode,
|
|
RSCApp,
|
|
ITEM_COUNT
|
|
);
|
|
console.log(
|
|
'Flight + Fizz (Node, sync): %d bytes',
|
|
flightFizzNodeHtml.length
|
|
);
|
|
|
|
const fizzNodeAsyncHtml = await renderFizzNode(AppAsync, ITEM_COUNT);
|
|
console.log('Fizz (Node, async): %d bytes', fizzNodeAsyncHtml.length);
|
|
|
|
const flightFizzNodeAsyncHtml = await renderFlightFizzNode(
|
|
renderRSCNode,
|
|
RSCAppAsync,
|
|
ITEM_COUNT
|
|
);
|
|
console.log(
|
|
'Flight + Fizz (Node, async):%d bytes',
|
|
flightFizzNodeAsyncHtml.length
|
|
);
|
|
|
|
const fizzEdgeHtml = await renderFizzEdge(App, ITEM_COUNT);
|
|
console.log('Fizz (Edge, sync): %d bytes', fizzEdgeHtml.length);
|
|
|
|
const fizzEdgeAsyncHtml = await renderFizzEdge(AppAsync, ITEM_COUNT);
|
|
console.log('Fizz (Edge, async): %d bytes', fizzEdgeAsyncHtml.length);
|
|
|
|
const flightFizzEdgeHtml = await renderFlightFizzEdge(
|
|
renderRSCEdge,
|
|
RSCApp,
|
|
ITEM_COUNT
|
|
);
|
|
console.log(
|
|
'Flight + Fizz (Edge, sync): %d bytes',
|
|
flightFizzEdgeHtml.length
|
|
);
|
|
|
|
const flightFizzEdgeAsyncHtml = await renderFlightFizzEdge(
|
|
renderRSCEdge,
|
|
RSCAppAsync,
|
|
ITEM_COUNT
|
|
);
|
|
console.log(
|
|
'Flight + Fizz (Edge, async):%d bytes',
|
|
flightFizzEdgeAsyncHtml.length
|
|
);
|
|
|
|
// --- CPU Profiling ---
|
|
if (PROFILE_MODE) {
|
|
console.log(
|
|
'\n--- CPU Profiling (%d warmup, %d iterations) ---\n',
|
|
PROFILE_WARMUP,
|
|
PROFILE_ITERATIONS
|
|
);
|
|
|
|
const profileDir = path.resolve(__dirname, 'build/profiles');
|
|
|
|
await profileRun(
|
|
'Fizz (Node, sync)',
|
|
() => renderFizzNode(App, ITEM_COUNT),
|
|
PROFILE_WARMUP,
|
|
PROFILE_ITERATIONS,
|
|
path.join(profileDir, 'fizz-node-sync.cpuprofile')
|
|
);
|
|
|
|
await profileRun(
|
|
'Flight + Fizz (Node, sync)',
|
|
() => renderFlightFizzNode(renderRSCNode, RSCApp, ITEM_COUNT),
|
|
PROFILE_WARMUP,
|
|
PROFILE_ITERATIONS,
|
|
path.join(profileDir, 'flight-fizz-node-sync.cpuprofile')
|
|
);
|
|
|
|
await profileRun(
|
|
'Fizz (Node, async)',
|
|
() => renderFizzNode(AppAsync, ITEM_COUNT),
|
|
PROFILE_WARMUP,
|
|
PROFILE_ITERATIONS,
|
|
path.join(profileDir, 'fizz-node-async.cpuprofile')
|
|
);
|
|
|
|
await profileRun(
|
|
'Flight + Fizz (Node, async)',
|
|
() => renderFlightFizzNode(renderRSCNode, RSCAppAsync, ITEM_COUNT),
|
|
PROFILE_WARMUP,
|
|
PROFILE_ITERATIONS,
|
|
path.join(profileDir, 'flight-fizz-node-async.cpuprofile')
|
|
);
|
|
|
|
await profileRun(
|
|
'Fizz (Edge, sync)',
|
|
() => renderFizzEdge(App, ITEM_COUNT),
|
|
PROFILE_WARMUP,
|
|
PROFILE_ITERATIONS,
|
|
path.join(profileDir, 'fizz-edge-sync.cpuprofile')
|
|
);
|
|
|
|
await profileRun(
|
|
'Flight + Fizz (Edge, sync)',
|
|
() => renderFlightFizzEdge(renderRSCEdge, RSCApp, ITEM_COUNT),
|
|
PROFILE_WARMUP,
|
|
PROFILE_ITERATIONS,
|
|
path.join(profileDir, 'flight-fizz-edge-sync.cpuprofile')
|
|
);
|
|
|
|
await profileRun(
|
|
'Fizz (Edge, async)',
|
|
() => renderFizzEdge(AppAsync, ITEM_COUNT),
|
|
PROFILE_WARMUP,
|
|
PROFILE_ITERATIONS,
|
|
path.join(profileDir, 'fizz-edge-async.cpuprofile')
|
|
);
|
|
|
|
await profileRun(
|
|
'Flight + Fizz (Edge, async)',
|
|
() => renderFlightFizzEdge(renderRSCEdge, RSCAppAsync, ITEM_COUNT),
|
|
PROFILE_WARMUP,
|
|
PROFILE_ITERATIONS,
|
|
path.join(profileDir, 'flight-fizz-edge-async.cpuprofile')
|
|
);
|
|
|
|
console.log(
|
|
'\nProfiles saved to build/profiles/. Open in Chrome DevTools or speedscope.app.'
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
// --- Concurrent Benchmark ---
|
|
if (CONCURRENT_MODE) {
|
|
const CONCURRENCY = 50;
|
|
const TOTAL = 1000;
|
|
const CONC_WARMUP = 20;
|
|
|
|
console.log(
|
|
'\n--- Concurrent Benchmark (%d warmup, %d concurrency, %d requests, %d items) ---\n',
|
|
CONC_WARMUP,
|
|
CONCURRENCY,
|
|
TOTAL,
|
|
ITEM_COUNT
|
|
);
|
|
|
|
const fizzNodeSync = await runConcurrent(
|
|
'Fizz (Node, sync)',
|
|
() => renderFizzNode(App, ITEM_COUNT),
|
|
TOTAL,
|
|
CONCURRENCY,
|
|
CONC_WARMUP
|
|
);
|
|
printConcurrentResult(fizzNodeSync);
|
|
|
|
const flightFizzNodeSync = await runConcurrent(
|
|
'Flight + Fizz (Node, sync)',
|
|
() => renderFlightFizzNode(renderRSCNode, RSCApp, ITEM_COUNT),
|
|
TOTAL,
|
|
CONCURRENCY,
|
|
CONC_WARMUP
|
|
);
|
|
printConcurrentResult(flightFizzNodeSync);
|
|
|
|
const fizzNodeAsync = await runConcurrent(
|
|
'Fizz (Node, async)',
|
|
() => renderFizzNode(AppAsync, ITEM_COUNT),
|
|
TOTAL,
|
|
CONCURRENCY,
|
|
CONC_WARMUP
|
|
);
|
|
printConcurrentResult(fizzNodeAsync);
|
|
|
|
const flightFizzNodeAsync = await runConcurrent(
|
|
'Flight + Fizz (Node, async)',
|
|
() => renderFlightFizzNode(renderRSCNode, RSCAppAsync, ITEM_COUNT),
|
|
TOTAL,
|
|
CONCURRENCY,
|
|
CONC_WARMUP
|
|
);
|
|
printConcurrentResult(flightFizzNodeAsync);
|
|
|
|
const fizzEdgeSync = await runConcurrent(
|
|
'Fizz (Edge, sync)',
|
|
() => renderFizzEdge(App, ITEM_COUNT),
|
|
TOTAL,
|
|
CONCURRENCY,
|
|
CONC_WARMUP
|
|
);
|
|
printConcurrentResult(fizzEdgeSync);
|
|
|
|
const flightFizzEdgeSync = await runConcurrent(
|
|
'Flight + Fizz (Edge, sync)',
|
|
() => renderFlightFizzEdge(renderRSCEdge, RSCApp, ITEM_COUNT),
|
|
TOTAL,
|
|
CONCURRENCY,
|
|
CONC_WARMUP
|
|
);
|
|
printConcurrentResult(flightFizzEdgeSync);
|
|
|
|
const fizzEdgeAsync = await runConcurrent(
|
|
'Fizz (Edge, async)',
|
|
() => renderFizzEdge(AppAsync, ITEM_COUNT),
|
|
TOTAL,
|
|
CONCURRENCY,
|
|
CONC_WARMUP
|
|
);
|
|
printConcurrentResult(fizzEdgeAsync);
|
|
|
|
const flightFizzEdgeAsync = await runConcurrent(
|
|
'Flight + Fizz (Edge, async)',
|
|
() => renderFlightFizzEdge(renderRSCEdge, RSCAppAsync, ITEM_COUNT),
|
|
TOTAL,
|
|
CONCURRENCY,
|
|
CONC_WARMUP
|
|
);
|
|
printConcurrentResult(flightFizzEdgeAsync);
|
|
|
|
const rps = r => r.reqPerSec;
|
|
|
|
console.log('\n--- Flight overhead ---\n');
|
|
printGrid(
|
|
['Fizz', 'Flight+Fizz'],
|
|
[
|
|
['Node sync', fizzNodeSync, flightFizzNodeSync],
|
|
['Node async', fizzNodeAsync, flightFizzNodeAsync],
|
|
['Edge sync', fizzEdgeSync, flightFizzEdgeSync],
|
|
['Edge async', fizzEdgeAsync, flightFizzEdgeAsync],
|
|
],
|
|
rps,
|
|
'req/s',
|
|
'higher is better'
|
|
);
|
|
|
|
console.log('\n--- Edge vs Node ---\n');
|
|
printGrid(
|
|
['Node', 'Edge'],
|
|
[
|
|
['Fizz sync', fizzNodeSync, fizzEdgeSync],
|
|
['Fizz async', fizzNodeAsync, fizzEdgeAsync],
|
|
['Flight+Fizz sync', flightFizzNodeSync, flightFizzEdgeSync],
|
|
['Flight+Fizz async', flightFizzNodeAsync, flightFizzEdgeAsync],
|
|
],
|
|
rps,
|
|
'req/s',
|
|
'higher is better'
|
|
);
|
|
|
|
writeJsonOut('concurrent');
|
|
return;
|
|
}
|
|
|
|
// --- Benchmark ---
|
|
console.log(
|
|
'\n--- Benchmark (%d warmup, %d iterations, %d items) ---\n',
|
|
WARMUP,
|
|
ITERATIONS,
|
|
ITEM_COUNT
|
|
);
|
|
|
|
const fizzNodeSync = await runBenchmark(
|
|
'Fizz (Node, sync)',
|
|
() => renderFizzNode(App, ITEM_COUNT),
|
|
ITERATIONS,
|
|
WARMUP
|
|
);
|
|
printResult(fizzNodeSync);
|
|
|
|
const flightFizzNodeSync = await runBenchmark(
|
|
'Flight + Fizz (Node, sync)',
|
|
() => renderFlightFizzNode(renderRSCNode, RSCApp, ITEM_COUNT),
|
|
ITERATIONS,
|
|
WARMUP
|
|
);
|
|
printResult(flightFizzNodeSync);
|
|
|
|
const fizzNodeAsync = await runBenchmark(
|
|
'Fizz (Node, async)',
|
|
() => renderFizzNode(AppAsync, ITEM_COUNT),
|
|
ITERATIONS,
|
|
WARMUP
|
|
);
|
|
printResult(fizzNodeAsync);
|
|
|
|
const flightFizzNodeAsync = await runBenchmark(
|
|
'Flight + Fizz (Node, async)',
|
|
() => renderFlightFizzNode(renderRSCNode, RSCAppAsync, ITEM_COUNT),
|
|
ITERATIONS,
|
|
WARMUP
|
|
);
|
|
printResult(flightFizzNodeAsync);
|
|
|
|
const fizzEdgeSync = await runBenchmark(
|
|
'Fizz (Edge, sync)',
|
|
() => renderFizzEdge(App, ITEM_COUNT),
|
|
ITERATIONS,
|
|
WARMUP
|
|
);
|
|
printResult(fizzEdgeSync);
|
|
|
|
const flightFizzEdgeSync = await runBenchmark(
|
|
'Flight + Fizz (Edge, sync)',
|
|
() => renderFlightFizzEdge(renderRSCEdge, RSCApp, ITEM_COUNT),
|
|
ITERATIONS,
|
|
WARMUP
|
|
);
|
|
printResult(flightFizzEdgeSync);
|
|
|
|
const fizzEdgeAsync = await runBenchmark(
|
|
'Fizz (Edge, async)',
|
|
() => renderFizzEdge(AppAsync, ITEM_COUNT),
|
|
ITERATIONS,
|
|
WARMUP
|
|
);
|
|
printResult(fizzEdgeAsync);
|
|
|
|
const flightFizzEdgeAsync = await runBenchmark(
|
|
'Flight + Fizz (Edge, async)',
|
|
() => renderFlightFizzEdge(renderRSCEdge, RSCAppAsync, ITEM_COUNT),
|
|
ITERATIONS,
|
|
WARMUP
|
|
);
|
|
printResult(flightFizzEdgeAsync);
|
|
|
|
const median = r => r.median;
|
|
|
|
console.log('\n--- Flight overhead ---\n');
|
|
printGrid(
|
|
['Fizz', 'Flight+Fizz'],
|
|
[
|
|
['Node sync', fizzNodeSync, flightFizzNodeSync],
|
|
['Node async', fizzNodeAsync, flightFizzNodeAsync],
|
|
['Edge sync', fizzEdgeSync, flightFizzEdgeSync],
|
|
['Edge async', fizzEdgeAsync, flightFizzEdgeAsync],
|
|
],
|
|
median,
|
|
'ms',
|
|
'median, lower is better'
|
|
);
|
|
|
|
console.log('\n--- Edge vs Node ---\n');
|
|
printGrid(
|
|
['Node', 'Edge'],
|
|
[
|
|
['Fizz sync', fizzNodeSync, fizzEdgeSync],
|
|
['Fizz async', fizzNodeAsync, fizzEdgeAsync],
|
|
['Flight+Fizz sync', flightFizzNodeSync, flightFizzEdgeSync],
|
|
['Flight+Fizz async', flightFizzNodeAsync, flightFizzEdgeAsync],
|
|
],
|
|
median,
|
|
'ms',
|
|
'median, lower is better'
|
|
);
|
|
|
|
writeJsonOut(INJECT ? 'inject' : 'bare');
|
|
}
|
|
|
|
main().catch(function (err) {
|
|
console.error(err);
|
|
process.exit(1);
|
|
});
|