Commit f247eba
[Flight] Walk parsed JSON instead of using reviver for parsing RSC payload (facebook#35776)
## Summary
Follow-up to vercel/next.js#89823 with the
actual changes to React.
Replaces the `JSON.parse` reviver callback in `initializeModelChunk`
with a two-step approach: plain `JSON.parse()` followed by a recursive
`reviveModel()` post-process (same as in Flight Reply Server). This
yields a **~75% speedup** in RSC chunk deserialization.
| Payload | Original (ms) | Walk (ms) | Speedup |
|---------|---------------|-----------|---------|
| Small (2 elements, 142B) | 0.0024 | 0.0007 | **+72%** |
| Medium (~12 elements, 914B) | 0.0116 | 0.0031 | **+73%** |
| Large (~90 elements, 16.7KB) | 0.1836 | 0.0451 | **+75%** |
| XL (~200 elements, 25.7KB) | 0.3742 | 0.0913 | **+76%** |
| Table (1000 rows, 110KB) | 3.0862 | 0.6887 | **+78%** |
## Problem
`createFromJSONCallback` returns a reviver function passed as the second
argument to `JSON.parse()`. This reviver is called for **every key-value
pair** in the parsed JSON. While the logic inside the reviver is
lightweight, the dominant cost is the **C++ → JavaScript boundary
crossing** — V8's `JSON.parse` is implemented in C++, and calling back
into JavaScript for every node incurs significant overhead.
Even a trivial no-op reviver `(k, v) => v` makes `JSON.parse` **~4x
slower** than bare `JSON.parse` without a reviver:
```
108 KB payload:
Bare JSON.parse: 0.60 ms
Trivial reviver: 2.95 ms (+391%)
```
## Change
Replace the reviver with a two-step process:
1. `JSON.parse(resolvedModel)` — parse the entire payload in C++ with no
callbacks
2. `reviveModel` — recursively walk the resulting object in pure
JavaScript to apply RSC transformations
The `reviveModel` function includes additional optimizations over the
original reviver:
- **Short-circuits plain strings**: only calls `parseModelString` when
the string starts with `$`, skipping the vast majority of strings (class
names, text content, etc.)
- **Stays entirely in JavaScript** — no C++ boundary crossings during
the walk
## Results
You can find the related applications in the [Next.js PR
](vercel/next.js#89823 I've been testing this
on Next.js applications.
### Table as Server Component with 1000 items
Before:
```
"min": 13.782875000000786,
"max": 22.23400000000038,
"avg": 17.116868530000083,
"p50": 17.10766700000022,
"p75": 18.50787499999933,
"p95": 20.426249999998618,
"p99": 21.814125000000786
```
After:
```
"min": 10.963916999999128,
"max": 18.096083000000363,
"avg": 13.543286884999988,
"p50": 13.58350000000064,
"p75": 14.871791999999914,
"p95": 16.08429099999921,
"p99": 17.591458000000785
```
### Table as Client Component with 1000 items
Before:
```
"min": 3.888875000000553,
"max": 9.044959000000745,
"avg": 4.651271475000067,
"p50": 4.555749999999534,
"p75": 4.966624999999112,
"p95": 5.47754200000054,
"p99": 6.109499999998661
````
After:
```
"min": 3.5986250000005384,
"max": 5.374291000000085,
"avg": 4.142990245000046,
"p50": 4.10570799999914,
"p75": 4.392041999999492,
"p95": 4.740084000000934,
"p99": 5.1652500000000146
```
### Nested Suspense
Before:
```
Requests: 200
Min: 73ms
Max: 106ms
Avg: 78ms
P50: 77ms
P75: 80ms
P95: 85ms
P99: 94ms
```
After:
```
Requests: 200
Min: 56ms
Max: 67ms
Avg: 59ms
P50: 58ms
P75: 60ms
P95: 65ms
P99: 66ms
```
### Even more nested Suspense (double-level Suspense)
Before:
```
Requests: 200
Min: 159ms
Max: 208ms
Avg: 169ms
P50: 167ms
P75: 173ms
P95: 183ms
P99: 188ms
```
After:
```
Requests: 200
Min: 125ms
Max: 170ms
Avg: 134ms
P50: 132ms
P75: 138ms
P95: 148ms
P99: 160ms
```
## How did you test this change?
Ran it across many Next.js benchmark applications.
The entire Next.js test suite passes with this change.
---------
Co-authored-by: Hendrik Liebau <mail@hendrik-liebau.de>1 parent 3a2bee2 commit f247eba
File tree
2 files changed
+49
-19
lines changed- packages
- react-client/src
- react-noop-renderer/src
2 files changed
+49
-19
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
355 | 355 | | |
356 | 356 | | |
357 | 357 | | |
358 | | - | |
359 | 358 | | |
360 | 359 | | |
361 | 360 | | |
| |||
2302 | 2301 | | |
2303 | 2302 | | |
2304 | 2303 | | |
| 2304 | + | |
| 2305 | + | |
| 2306 | + | |
| 2307 | + | |
| 2308 | + | |
2305 | 2309 | | |
2306 | 2310 | | |
2307 | 2311 | | |
| |||
2622 | 2626 | | |
2623 | 2627 | | |
2624 | 2628 | | |
| 2629 | + | |
| 2630 | + | |
| 2631 | + | |
| 2632 | + | |
| 2633 | + | |
2625 | 2634 | | |
2626 | 2635 | | |
2627 | 2636 | | |
| |||
2699 | 2708 | | |
2700 | 2709 | | |
2701 | 2710 | | |
2702 | | - | |
2703 | 2711 | | |
2704 | 2712 | | |
2705 | 2713 | | |
| |||
2783 | 2791 | | |
2784 | 2792 | | |
2785 | 2793 | | |
2786 | | - | |
2787 | | - | |
2788 | | - | |
2789 | 2794 | | |
2790 | 2795 | | |
2791 | 2796 | | |
| |||
5259 | 5264 | | |
5260 | 5265 | | |
5261 | 5266 | | |
5262 | | - | |
| 5267 | + | |
| 5268 | + | |
| 5269 | + | |
| 5270 | + | |
| 5271 | + | |
5263 | 5272 | | |
5264 | 5273 | | |
5265 | | - | |
5266 | | - | |
5267 | | - | |
5268 | | - | |
5269 | | - | |
| 5274 | + | |
| 5275 | + | |
| 5276 | + | |
| 5277 | + | |
| 5278 | + | |
| 5279 | + | |
| 5280 | + | |
| 5281 | + | |
| 5282 | + | |
5270 | 5283 | | |
5271 | | - | |
5272 | | - | |
5273 | | - | |
| 5284 | + | |
| 5285 | + | |
| 5286 | + | |
| 5287 | + | |
| 5288 | + | |
| 5289 | + | |
| 5290 | + | |
| 5291 | + | |
5274 | 5292 | | |
5275 | | - | |
| 5293 | + | |
| 5294 | + | |
5276 | 5295 | | |
5277 | 5296 | | |
5278 | 5297 | | |
5279 | | - | |
| 5298 | + | |
| 5299 | + | |
| 5300 | + | |
| 5301 | + | |
| 5302 | + | |
| 5303 | + | |
| 5304 | + | |
| 5305 | + | |
| 5306 | + | |
| 5307 | + | |
| 5308 | + | |
| 5309 | + | |
| 5310 | + | |
| 5311 | + | |
| 5312 | + | |
5280 | 5313 | | |
5281 | 5314 | | |
5282 | 5315 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
43 | 43 | | |
44 | 44 | | |
45 | 45 | | |
46 | | - | |
47 | | - | |
48 | | - | |
49 | 46 | | |
50 | 47 | | |
51 | 48 | | |
| |||
0 commit comments