main
js 792 lines 21.1 KB
Raw
1 'use strict';
2
3 require('@babel/register')({
4 presets: [['@babel/preset-react', {runtime: 'automatic'}]],
5 plugins: ['@babel/plugin-transform-modules-commonjs'],
6 only: [/\/src\//],
7 });
8
9 const path = require('path');
10 const fs = require('fs');
11 const webpack = require('webpack');
12 const inspector = require('node:inspector');
13
14 const {clientManifest, ssrManifest} = require('./webpack-mock');
15
16 const PROFILE_MODE = process.argv.includes('--profile');
17 const CONCURRENT_MODE = process.argv.includes('--concurrent');
18 const INJECT = !process.argv.includes('--no-injection');
19 const JSON_OUT = (function () {
20 const arg = process.argv.find(function (a) {
21 return a.startsWith('--json-out=');
22 });
23 return arg ? arg.slice('--json-out='.length) : null;
24 })();
25 const jsonResults = [];
26 function writeJsonOut(mode) {
27 if (!JSON_OUT) return;
28 fs.writeFileSync(JSON_OUT, JSON.stringify({mode, results: jsonResults}));
29 }
30
31 // ---------------------------------------------------------------------------
32 // Build
33 // ---------------------------------------------------------------------------
34
35 function build() {
36 const config = require('./webpack.config');
37 return new Promise(function (resolve, reject) {
38 webpack(config, function (err, stats) {
39 if (err) {
40 reject(err);
41 return;
42 }
43 if (stats.hasErrors()) {
44 reject(new Error(stats.toString({errors: true})));
45 return;
46 }
47 console.log(
48 stats.toString({colors: true, modules: false, entrypoints: false})
49 );
50 resolve();
51 });
52 });
53 }
54
55 // ---------------------------------------------------------------------------
56 // Render helpers
57 // ---------------------------------------------------------------------------
58
59 const {
60 renderFizzNode: renderFizzNodeStream,
61 renderFizzEdge: renderFizzEdgeStream,
62 renderFlightFizzNode: renderFlightFizzNodeStream,
63 renderFlightFizzEdge: renderFlightFizzEdgeStream,
64 nodeStreamToString,
65 webStreamToString,
66 } = require('./render-helpers');
67 const {printGrid} = require('./print-helpers');
68
69 function renderFizzNode(AppComponent, itemCount) {
70 return nodeStreamToString(renderFizzNodeStream(AppComponent, itemCount));
71 }
72
73 function renderFizzEdge(AppComponent, itemCount) {
74 return renderFizzEdgeStream(AppComponent, itemCount).then(webStreamToString);
75 }
76
77 function renderFlightFizzNode(renderRSCNode, AppComponent, itemCount) {
78 return nodeStreamToString(
79 renderFlightFizzNodeStream(
80 renderRSCNode,
81 AppComponent,
82 itemCount,
83 clientManifest,
84 ssrManifest,
85 {inject: INJECT}
86 )
87 );
88 }
89
90 function renderFlightFizzEdge(renderRSCEdge, AppComponent, itemCount) {
91 return renderFlightFizzEdgeStream(
92 renderRSCEdge,
93 AppComponent,
94 itemCount,
95 clientManifest,
96 ssrManifest,
97 {inject: INJECT}
98 ).then(webStreamToString);
99 }
100
101 // ---------------------------------------------------------------------------
102 // Benchmarking
103 // ---------------------------------------------------------------------------
104
105 const canGC = typeof globalThis.gc === 'function';
106
107 // Yield to the event loop's check phase between renders. React schedules a
108 // setImmediate per request, but a fully in-process render completes entirely
109 // in microtasks/nextTicks, so a tight benchmark loop never lets those
110 // immediates run. They then pile up in Node's immediate queue, each one
111 // retaining its (otherwise finished) request graph, which inflates any memory
112 // measurement. A real server yields to the event loop on every request, so
113 // draining between iterations is both correct and more representative.
114 function tick() {
115 return new Promise(resolve => setImmediate(resolve));
116 }
117
118 async function settledHeapUsed() {
119 await tick();
120 if (canGC) globalThis.gc();
121 return process.memoryUsage().heapUsed;
122 }
123
124 async function runBenchmark(name, fn, iterations, warmup) {
125 if (canGC) globalThis.gc();
126 const heapBefore = await settledHeapUsed();
127
128 // Warmup
129 for (let i = 0; i < warmup; i++) {
130 await fn();
131 await tick();
132 }
133
134 // Collect GC pauses during timed iterations.
135 let gcCount = 0;
136 let gcTotalMs = 0;
137 const gcObs = new PerformanceObserver(list => {
138 for (const entry of list.getEntries()) {
139 gcCount++;
140 gcTotalMs += entry.duration;
141 }
142 });
143 gcObs.observe({entryTypes: ['gc']});
144
145 // Timed iterations. The tick between iterations is excluded from the
146 // timed window.
147 const times = [];
148 for (let i = 0; i < iterations; i++) {
149 const start = performance.now();
150 await fn();
151 times.push(performance.now() - start);
152 await tick();
153 }
154 gcObs.disconnect();
155 const heapAfter = await settledHeapUsed();
156
157 // Trim top/bottom 5% to remove outliers
158 const sorted = [...times].sort((a, b) => a - b);
159 const trimCount = Math.floor(sorted.length * 0.05);
160 const trimmed = sorted.slice(trimCount, sorted.length - trimCount);
161
162 const mean = trimmed.reduce((s, t) => s + t, 0) / trimmed.length;
163 const median = sorted[Math.floor(sorted.length / 2)];
164 const stddev = Math.sqrt(
165 trimmed.reduce((s, t) => s + (t - mean) ** 2, 0) / trimmed.length
166 );
167 const p95 = sorted[Math.floor(sorted.length * 0.95)];
168 const min = sorted[0];
169 const max = sorted[sorted.length - 1];
170
171 return {
172 name,
173 mean,
174 median,
175 stddev,
176 p95,
177 min,
178 max,
179 iterations,
180 gcCount,
181 gcTotalMs,
182 heapBefore,
183 heapAfter,
184 };
185 }
186
187 function printResult(result) {
188 jsonResults.push(result);
189 console.log(' %s:', result.name);
190 console.log(' Mean: %s ms', result.mean.toFixed(2));
191 console.log(' Median: %s ms', result.median.toFixed(2));
192 console.log(' Stddev: %s ms', result.stddev.toFixed(2));
193 console.log(' P95: %s ms', result.p95.toFixed(2));
194 console.log(' Min: %s ms', result.min.toFixed(2));
195 console.log(' Max: %s ms', result.max.toFixed(2));
196 console.log(
197 ' GC: %d pauses, %s ms total (%s ms/iter)',
198 result.gcCount,
199 result.gcTotalMs.toFixed(1),
200 (result.gcTotalMs / result.iterations).toFixed(2)
201 );
202 printHeap(result);
203 }
204
205 function printHeap(result) {
206 if (!canGC) return;
207 const mb = b => (b / 1048576).toFixed(1);
208 const delta = result.heapAfter - result.heapBefore;
209 console.log(
210 ' Heap: %s MB retained after run (%s%s MB vs before)',
211 mb(result.heapAfter),
212 delta >= 0 ? '+' : '',
213 mb(delta)
214 );
215 }
216
217 async function runConcurrent(name, fn, total, concurrency, warmup) {
218 if (canGC) globalThis.gc();
219 const heapBefore = await settledHeapUsed();
220
221 for (let i = 0; i < warmup; i++) {
222 await fn();
223 await tick();
224 }
225
226 let gcCount = 0;
227 let gcTotalMs = 0;
228 const gcObs = new PerformanceObserver(list => {
229 for (const entry of list.getEntries()) {
230 gcCount++;
231 gcTotalMs += entry.duration;
232 }
233 });
234 gcObs.observe({entryTypes: ['gc']});
235
236 const latencies = new Array(total);
237 let completed = 0;
238 let launched = 0;
239
240 const start = performance.now();
241 await new Promise(resolve => {
242 function launch() {
243 while (launched < total && launched - completed < concurrency) {
244 const idx = launched++;
245 const t0 = performance.now();
246 fn()
247 .then(() => {
248 latencies[idx] = performance.now() - t0;
249 // Drain immediates before freeing the slot (see tick()).
250 return tick();
251 })
252 .then(() => {
253 completed++;
254 if (completed === total) {
255 resolve();
256 } else {
257 launch();
258 }
259 });
260 }
261 }
262 launch();
263 });
264 const elapsed = performance.now() - start;
265 gcObs.disconnect();
266 const heapAfter = await settledHeapUsed();
267
268 const sorted = [...latencies].sort((a, b) => a - b);
269 const mean = sorted.reduce((s, t) => s + t, 0) / sorted.length;
270 const p95 = sorted[Math.floor(sorted.length * 0.95)];
271
272 return {
273 name,
274 reqPerSec: (total / elapsed) * 1000,
275 mean,
276 p95,
277 total,
278 concurrency,
279 gcCount,
280 gcTotalMs,
281 heapBefore,
282 heapAfter,
283 };
284 }
285
286 function printConcurrentResult(result) {
287 jsonResults.push(result);
288 console.log(' %s:', result.name);
289 console.log(' Req/s: %s', result.reqPerSec.toFixed(1));
290 console.log(' Mean: %s ms', result.mean.toFixed(2));
291 console.log(' P95: %s ms', result.p95.toFixed(2));
292 console.log(
293 ' GC: %d pauses, %s ms total (%s ms/req)',
294 result.gcCount,
295 result.gcTotalMs.toFixed(1),
296 (result.gcTotalMs / result.total).toFixed(2)
297 );
298 printHeap(result);
299 }
300
301 // ---------------------------------------------------------------------------
302 // CPU Profiling
303 // ---------------------------------------------------------------------------
304
305 function startProfiler() {
306 const session = new inspector.Session();
307 session.connect();
308 return new Promise(function (resolve, reject) {
309 session.post('Profiler.enable', function (err) {
310 if (err) {
311 reject(err);
312 return;
313 }
314 session.post('Profiler.start', function (err2) {
315 if (err2) {
316 reject(err2);
317 return;
318 }
319 resolve(session);
320 });
321 });
322 });
323 }
324
325 function stopProfiler(session, outputPath) {
326 return new Promise(function (resolve, reject) {
327 session.post('Profiler.stop', function (err, {profile}) {
328 if (err) {
329 reject(err);
330 return;
331 }
332 fs.mkdirSync(path.dirname(outputPath), {recursive: true});
333 fs.writeFileSync(outputPath, JSON.stringify(profile));
334 session.post('Profiler.disable');
335 session.disconnect();
336 resolve(profile);
337 });
338 });
339 }
340
341 function printTopFunctions(profile, topN) {
342 // Aggregate self-time per function from the profile nodes.
343 const selfTimes = new Map();
344 for (const node of profile.nodes) {
345 const name = node.callFrame.functionName || '(anonymous)';
346 const loc = node.callFrame.url
347 ? node.callFrame.url.replace(/.*\//, '') + ':' + node.callFrame.lineNumber
348 : '(native)';
349 const key = name + ' @ ' + loc;
350 const hitCount = node.hitCount || 0;
351 selfTimes.set(key, (selfTimes.get(key) || 0) + hitCount);
352 }
353
354 const sorted = [...selfTimes.entries()]
355 .sort((a, b) => b[1] - a[1])
356 .slice(0, topN);
357
358 const totalSamples = profile.nodes.reduce((s, n) => s + (n.hitCount || 0), 0);
359
360 console.log(' Top %d functions by self-time:', topN);
361 for (const [key, hits] of sorted) {
362 const pct = ((hits / totalSamples) * 100).toFixed(1);
363 console.log(' %s%% - %s', pct, key);
364 }
365 }
366
367 async function profileRun(name, fn, warmup, iterations, outputPath) {
368 // Warmup (unprofiled)
369 for (let i = 0; i < warmup; i++) {
370 await fn();
371 await tick();
372 }
373
374 // Collect GC pauses during the profiled run.
375 let gcCount = 0;
376 let gcTotalMs = 0;
377 const gcObs = new PerformanceObserver(list => {
378 for (const entry of list.getEntries()) {
379 gcCount++;
380 gcTotalMs += entry.duration;
381 }
382 });
383 gcObs.observe({entryTypes: ['gc']});
384
385 // Profiled run
386 const session = await startProfiler();
387 for (let i = 0; i < iterations; i++) {
388 await fn();
389 await tick();
390 }
391 const profile = await stopProfiler(session, outputPath);
392 gcObs.disconnect();
393
394 console.log(' %s → %s', name, outputPath);
395 printTopFunctions(profile, 10);
396 console.log(
397 ' GC: %d pauses, %s ms total (%s ms/iter)',
398 gcCount,
399 gcTotalMs.toFixed(1),
400 (gcTotalMs / iterations).toFixed(2)
401 );
402 }
403
404 // ---------------------------------------------------------------------------
405 // Main
406 // ---------------------------------------------------------------------------
407
408 async function main() {
409 console.log('Building RSC bundle...\n');
410 await build();
411
412 const {
413 renderRSCNode,
414 renderRSCEdge,
415 App: RSCApp,
416 AppAsync: RSCAppAsync,
417 } = require('./build/rsc-bundle.js');
418 const App = require('./src/App.js').default;
419 const AppAsync = require('./src/AppAsync.js').default;
420
421 const ITEM_COUNT = 200;
422
423 const WARMUP = 50;
424 const ITERATIONS = 1000;
425 const PROFILE_WARMUP = 50;
426 const PROFILE_ITERATIONS = 500;
427
428 // --- Verify renders ---
429 console.log('\n--- Verifying renders ---\n');
430
431 const fizzNodeHtml = await renderFizzNode(App, ITEM_COUNT);
432 console.log('Fizz (Node, sync): %d bytes', fizzNodeHtml.length);
433
434 const flightFizzNodeHtml = await renderFlightFizzNode(
435 renderRSCNode,
436 RSCApp,
437 ITEM_COUNT
438 );
439 console.log(
440 'Flight + Fizz (Node, sync): %d bytes',
441 flightFizzNodeHtml.length
442 );
443
444 const fizzNodeAsyncHtml = await renderFizzNode(AppAsync, ITEM_COUNT);
445 console.log('Fizz (Node, async): %d bytes', fizzNodeAsyncHtml.length);
446
447 const flightFizzNodeAsyncHtml = await renderFlightFizzNode(
448 renderRSCNode,
449 RSCAppAsync,
450 ITEM_COUNT
451 );
452 console.log(
453 'Flight + Fizz (Node, async):%d bytes',
454 flightFizzNodeAsyncHtml.length
455 );
456
457 const fizzEdgeHtml = await renderFizzEdge(App, ITEM_COUNT);
458 console.log('Fizz (Edge, sync): %d bytes', fizzEdgeHtml.length);
459
460 const fizzEdgeAsyncHtml = await renderFizzEdge(AppAsync, ITEM_COUNT);
461 console.log('Fizz (Edge, async): %d bytes', fizzEdgeAsyncHtml.length);
462
463 const flightFizzEdgeHtml = await renderFlightFizzEdge(
464 renderRSCEdge,
465 RSCApp,
466 ITEM_COUNT
467 );
468 console.log(
469 'Flight + Fizz (Edge, sync): %d bytes',
470 flightFizzEdgeHtml.length
471 );
472
473 const flightFizzEdgeAsyncHtml = await renderFlightFizzEdge(
474 renderRSCEdge,
475 RSCAppAsync,
476 ITEM_COUNT
477 );
478 console.log(
479 'Flight + Fizz (Edge, async):%d bytes',
480 flightFizzEdgeAsyncHtml.length
481 );
482
483 // --- CPU Profiling ---
484 if (PROFILE_MODE) {
485 console.log(
486 '\n--- CPU Profiling (%d warmup, %d iterations) ---\n',
487 PROFILE_WARMUP,
488 PROFILE_ITERATIONS
489 );
490
491 const profileDir = path.resolve(__dirname, 'build/profiles');
492
493 await profileRun(
494 'Fizz (Node, sync)',
495 () => renderFizzNode(App, ITEM_COUNT),
496 PROFILE_WARMUP,
497 PROFILE_ITERATIONS,
498 path.join(profileDir, 'fizz-node-sync.cpuprofile')
499 );
500
501 await profileRun(
502 'Flight + Fizz (Node, sync)',
503 () => renderFlightFizzNode(renderRSCNode, RSCApp, ITEM_COUNT),
504 PROFILE_WARMUP,
505 PROFILE_ITERATIONS,
506 path.join(profileDir, 'flight-fizz-node-sync.cpuprofile')
507 );
508
509 await profileRun(
510 'Fizz (Node, async)',
511 () => renderFizzNode(AppAsync, ITEM_COUNT),
512 PROFILE_WARMUP,
513 PROFILE_ITERATIONS,
514 path.join(profileDir, 'fizz-node-async.cpuprofile')
515 );
516
517 await profileRun(
518 'Flight + Fizz (Node, async)',
519 () => renderFlightFizzNode(renderRSCNode, RSCAppAsync, ITEM_COUNT),
520 PROFILE_WARMUP,
521 PROFILE_ITERATIONS,
522 path.join(profileDir, 'flight-fizz-node-async.cpuprofile')
523 );
524
525 await profileRun(
526 'Fizz (Edge, sync)',
527 () => renderFizzEdge(App, ITEM_COUNT),
528 PROFILE_WARMUP,
529 PROFILE_ITERATIONS,
530 path.join(profileDir, 'fizz-edge-sync.cpuprofile')
531 );
532
533 await profileRun(
534 'Flight + Fizz (Edge, sync)',
535 () => renderFlightFizzEdge(renderRSCEdge, RSCApp, ITEM_COUNT),
536 PROFILE_WARMUP,
537 PROFILE_ITERATIONS,
538 path.join(profileDir, 'flight-fizz-edge-sync.cpuprofile')
539 );
540
541 await profileRun(
542 'Fizz (Edge, async)',
543 () => renderFizzEdge(AppAsync, ITEM_COUNT),
544 PROFILE_WARMUP,
545 PROFILE_ITERATIONS,
546 path.join(profileDir, 'fizz-edge-async.cpuprofile')
547 );
548
549 await profileRun(
550 'Flight + Fizz (Edge, async)',
551 () => renderFlightFizzEdge(renderRSCEdge, RSCAppAsync, ITEM_COUNT),
552 PROFILE_WARMUP,
553 PROFILE_ITERATIONS,
554 path.join(profileDir, 'flight-fizz-edge-async.cpuprofile')
555 );
556
557 console.log(
558 '\nProfiles saved to build/profiles/. Open in Chrome DevTools or speedscope.app.'
559 );
560
561 return;
562 }
563
564 // --- Concurrent Benchmark ---
565 if (CONCURRENT_MODE) {
566 const CONCURRENCY = 50;
567 const TOTAL = 1000;
568 const CONC_WARMUP = 20;
569
570 console.log(
571 '\n--- Concurrent Benchmark (%d warmup, %d concurrency, %d requests, %d items) ---\n',
572 CONC_WARMUP,
573 CONCURRENCY,
574 TOTAL,
575 ITEM_COUNT
576 );
577
578 const fizzNodeSync = await runConcurrent(
579 'Fizz (Node, sync)',
580 () => renderFizzNode(App, ITEM_COUNT),
581 TOTAL,
582 CONCURRENCY,
583 CONC_WARMUP
584 );
585 printConcurrentResult(fizzNodeSync);
586
587 const flightFizzNodeSync = await runConcurrent(
588 'Flight + Fizz (Node, sync)',
589 () => renderFlightFizzNode(renderRSCNode, RSCApp, ITEM_COUNT),
590 TOTAL,
591 CONCURRENCY,
592 CONC_WARMUP
593 );
594 printConcurrentResult(flightFizzNodeSync);
595
596 const fizzNodeAsync = await runConcurrent(
597 'Fizz (Node, async)',
598 () => renderFizzNode(AppAsync, ITEM_COUNT),
599 TOTAL,
600 CONCURRENCY,
601 CONC_WARMUP
602 );
603 printConcurrentResult(fizzNodeAsync);
604
605 const flightFizzNodeAsync = await runConcurrent(
606 'Flight + Fizz (Node, async)',
607 () => renderFlightFizzNode(renderRSCNode, RSCAppAsync, ITEM_COUNT),
608 TOTAL,
609 CONCURRENCY,
610 CONC_WARMUP
611 );
612 printConcurrentResult(flightFizzNodeAsync);
613
614 const fizzEdgeSync = await runConcurrent(
615 'Fizz (Edge, sync)',
616 () => renderFizzEdge(App, ITEM_COUNT),
617 TOTAL,
618 CONCURRENCY,
619 CONC_WARMUP
620 );
621 printConcurrentResult(fizzEdgeSync);
622
623 const flightFizzEdgeSync = await runConcurrent(
624 'Flight + Fizz (Edge, sync)',
625 () => renderFlightFizzEdge(renderRSCEdge, RSCApp, ITEM_COUNT),
626 TOTAL,
627 CONCURRENCY,
628 CONC_WARMUP
629 );
630 printConcurrentResult(flightFizzEdgeSync);
631
632 const fizzEdgeAsync = await runConcurrent(
633 'Fizz (Edge, async)',
634 () => renderFizzEdge(AppAsync, ITEM_COUNT),
635 TOTAL,
636 CONCURRENCY,
637 CONC_WARMUP
638 );
639 printConcurrentResult(fizzEdgeAsync);
640
641 const flightFizzEdgeAsync = await runConcurrent(
642 'Flight + Fizz (Edge, async)',
643 () => renderFlightFizzEdge(renderRSCEdge, RSCAppAsync, ITEM_COUNT),
644 TOTAL,
645 CONCURRENCY,
646 CONC_WARMUP
647 );
648 printConcurrentResult(flightFizzEdgeAsync);
649
650 const rps = r => r.reqPerSec;
651
652 console.log('\n--- Flight overhead ---\n');
653 printGrid(
654 ['Fizz', 'Flight+Fizz'],
655 [
656 ['Node sync', fizzNodeSync, flightFizzNodeSync],
657 ['Node async', fizzNodeAsync, flightFizzNodeAsync],
658 ['Edge sync', fizzEdgeSync, flightFizzEdgeSync],
659 ['Edge async', fizzEdgeAsync, flightFizzEdgeAsync],
660 ],
661 rps,
662 'req/s',
663 'higher is better'
664 );
665
666 console.log('\n--- Edge vs Node ---\n');
667 printGrid(
668 ['Node', 'Edge'],
669 [
670 ['Fizz sync', fizzNodeSync, fizzEdgeSync],
671 ['Fizz async', fizzNodeAsync, fizzEdgeAsync],
672 ['Flight+Fizz sync', flightFizzNodeSync, flightFizzEdgeSync],
673 ['Flight+Fizz async', flightFizzNodeAsync, flightFizzEdgeAsync],
674 ],
675 rps,
676 'req/s',
677 'higher is better'
678 );
679
680 writeJsonOut('concurrent');
681 return;
682 }
683
684 // --- Benchmark ---
685 console.log(
686 '\n--- Benchmark (%d warmup, %d iterations, %d items) ---\n',
687 WARMUP,
688 ITERATIONS,
689 ITEM_COUNT
690 );
691
692 const fizzNodeSync = await runBenchmark(
693 'Fizz (Node, sync)',
694 () => renderFizzNode(App, ITEM_COUNT),
695 ITERATIONS,
696 WARMUP
697 );
698 printResult(fizzNodeSync);
699
700 const flightFizzNodeSync = await runBenchmark(
701 'Flight + Fizz (Node, sync)',
702 () => renderFlightFizzNode(renderRSCNode, RSCApp, ITEM_COUNT),
703 ITERATIONS,
704 WARMUP
705 );
706 printResult(flightFizzNodeSync);
707
708 const fizzNodeAsync = await runBenchmark(
709 'Fizz (Node, async)',
710 () => renderFizzNode(AppAsync, ITEM_COUNT),
711 ITERATIONS,
712 WARMUP
713 );
714 printResult(fizzNodeAsync);
715
716 const flightFizzNodeAsync = await runBenchmark(
717 'Flight + Fizz (Node, async)',
718 () => renderFlightFizzNode(renderRSCNode, RSCAppAsync, ITEM_COUNT),
719 ITERATIONS,
720 WARMUP
721 );
722 printResult(flightFizzNodeAsync);
723
724 const fizzEdgeSync = await runBenchmark(
725 'Fizz (Edge, sync)',
726 () => renderFizzEdge(App, ITEM_COUNT),
727 ITERATIONS,
728 WARMUP
729 );
730 printResult(fizzEdgeSync);
731
732 const flightFizzEdgeSync = await runBenchmark(
733 'Flight + Fizz (Edge, sync)',
734 () => renderFlightFizzEdge(renderRSCEdge, RSCApp, ITEM_COUNT),
735 ITERATIONS,
736 WARMUP
737 );
738 printResult(flightFizzEdgeSync);
739
740 const fizzEdgeAsync = await runBenchmark(
741 'Fizz (Edge, async)',
742 () => renderFizzEdge(AppAsync, ITEM_COUNT),
743 ITERATIONS,
744 WARMUP
745 );
746 printResult(fizzEdgeAsync);
747
748 const flightFizzEdgeAsync = await runBenchmark(
749 'Flight + Fizz (Edge, async)',
750 () => renderFlightFizzEdge(renderRSCEdge, RSCAppAsync, ITEM_COUNT),
751 ITERATIONS,
752 WARMUP
753 );
754 printResult(flightFizzEdgeAsync);
755
756 const median = r => r.median;
757
758 console.log('\n--- Flight overhead ---\n');
759 printGrid(
760 ['Fizz', 'Flight+Fizz'],
761 [
762 ['Node sync', fizzNodeSync, flightFizzNodeSync],
763 ['Node async', fizzNodeAsync, flightFizzNodeAsync],
764 ['Edge sync', fizzEdgeSync, flightFizzEdgeSync],
765 ['Edge async', fizzEdgeAsync, flightFizzEdgeAsync],
766 ],
767 median,
768 'ms',
769 'median, lower is better'
770 );
771
772 console.log('\n--- Edge vs Node ---\n');
773 printGrid(
774 ['Node', 'Edge'],
775 [
776 ['Fizz sync', fizzNodeSync, fizzEdgeSync],
777 ['Fizz async', fizzNodeAsync, fizzEdgeAsync],
778 ['Flight+Fizz sync', flightFizzNodeSync, flightFizzEdgeSync],
779 ['Flight+Fizz async', flightFizzNodeAsync, flightFizzEdgeAsync],
780 ],
781 median,
782 'ms',
783 'median, lower is better'
784 );
785
786 writeJsonOut(INJECT ? 'inject' : 'bare');
787 }
788
789 main().catch(function (err) {
790 console.error(err);
791 process.exit(1);
792 });