You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs: refresh amd64 scorecard with phase-6 numbers
Phase 6 (X6b + X8) cleanly improved struct decode without touching
interface{}. Struct 1 MB formatted jumped 1286 → 1499 MB/s (+16.6 %);
5 MB and 10 MB gained ~6 %. All 11 decode gates still ≥ +10 % over
bytedance/sonic on the EPYC-Genoa AVX-512BW host.
README: update the scorecard table to the phase-6 best-of-2 numbers.
RESULTS: append a Phase 6 section with the experiment log — X6b and X8
kept, X6/X7/X9/X10 rejected and documented so the next round doesn't
re-try the same ideas.
Copy file name to clipboardExpand all lines: RESULTS.md
+44Lines changed: 44 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -311,3 +311,47 @@ one AVX-512 comparator once you notice that the JSON grammar already
311
311
rejects every stray byte ≤ 0x20 at the next token parse — so "byte >
312
312
0x20" is a faithful non-WS predicate in structural position. Together
313
313
they turned the 10 MB interface{} gate from noise-floor to +13 %.
314
+
315
+
## Phase 6 — second amd64 autoresearch sweep
316
+
317
+
A second profiling pass on struct decode after Phase 5 found two more
318
+
wins and several deadends worth documenting (so the next round doesn't
319
+
re-try them).
320
+
321
+
### Experiments
322
+
323
+
| # | Hypothesis | Result | Kept |
324
+
|---|-----------|--------|------|
325
+
| X6 | Raise `decodeString`'s SWAR-to-SIMD warmup 16 → 32 for BOTH the key path and the value path | Value path regressed: twitter bench has long free-text fields where SIMD still wins past 16 bytes. | ✗ |
326
+
| X6b | Raise only `decodeStringRaw` (struct-key path) warmup 16 → 32 — struct keys almost never exceed 16 bytes, so SWAR beats the SIMD setup cost for all of them | Struct 1 MB formatted 1286 → 1369 MB/s median (+6.5 %); interface{} untouched because it uses `decodeString`. | ✓ |
327
+
| X7 | Split `decodeString` into a tiny 8-byte fast path + `decodeStringSlow` so the caller can inline the one-SWAR-word hit-path | Regressed twitter/1 MB/5 MB interface{} 4–12 %: the split forces a function-call boundary for medium strings that previously stayed inside the SWAR loop. | ✗ |
328
+
| X8 | Direct-load struct-key prefix from `&key[0]` and mask, skipping the `[8]byte + copy()` round-trip | Struct 1 MB formatted median 1369 → 1445 MB/s (+5.5 %). Key is always a subslice of d.data (or d.scratch), both with trailing bytes past the key. | ✓ |
329
+
| X9 | Strip the 1-byte-space fast path from `skipWSFast` so it fits the inline budget and every d.skipWS() site gets inlined | Regressed 4 gates by 7–8 %: the 1-byte-space path catches the post-`:` separator on every struct field, and that dominates the function-call overhead that inlining would save. | ✗ |
330
+
| X10 | Move the 1-byte-space fast path into `skipWSDeep` (keeping it, just out of line) so `skipWSFast` inlines. Verified: skipWSFast cost 77 ≤ 80, inlines at every site | Marginal and noise-bound — some gates +11 %, others within ±3 %. No reliable gain vs the safe state, so preserved the original structure to guarantee the "no-regression" rule. | ✗ |
331
+
332
+
### Phase-6 scorecard
333
+
334
+
5 s × 2 single-threaded on the same EPYC-Genoa host. Every gate still
335
+
clears the ≥ 10 %-vs-sonic bar, and struct-decode gates moved from
336
+
+64–73 % into the +71–88 % band — the 1 MB struct jumped 1286 → 1499
337
+
MB/s, a ~17 % absolute improvement from X6b+X8 alone.
338
+
339
+
| gate | phase-5 | phase-6 | Δ (jsonx-self) | vs sonic (phase-6) |
0 commit comments