Skip to content

Commit e8388b4

Browse files
committed
docs: record GitHub cold path win after nested FASS
Stateless GitHub 7-field extract ~5.6µs (was ~37–47µs), ahead of sonic arm64 ~14µs; document that nested object-arrays no longer ban strides.
1 parent 80a9825 commit e8388b4

3 files changed

Lines changed: 31 additions & 28 deletions

File tree

README.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -566,8 +566,7 @@ Representative results on an Apple M4 Pro (lower is better):
566566
| Deep access + tape | **1.58 µs** / 0 B | — | — |
567567
568568
`jseek` leads the lazy class on single-field, multi-path, deep homogeneous arrays
569-
(FASS), and IndexTape reuse — all **zero allocation**. Deep `users[250]` is also
570-
ahead of sonic's arm64 path (~22.7 µs). **It is not universally fastest:** tiny
571-
flat NDJSON field-by-field and some nested non-uniform cold shapes can still
572-
favor gjson/sonic — see [`docs/BENCHMARKS.md`](docs/BENCHMARKS.md) for the full
573-
honest boundaries, methodology, and how to reproduce.
569+
(FASS), and IndexTape reuse — all **zero allocation**. Deep `users[250]` (~3 µs) and GitHub-style issue arrays (~5.6 µs cold) are both
570+
ahead of sonic's arm64 path. **It is not universally fastest:** tiny flat NDJSON
571+
field-by-field can still favor gjson by single-digit percent — see
572+
[`docs/BENCHMARKS.md`](docs/BENCHMARKS.md) for full boundaries and methodology.

docs/ARCHITECTURE.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -112,11 +112,11 @@ gate that catches `$0-N` / `ret` vs `ret1` mistakes).
112112
### Array strides (FASS)
113113

114114
`findIndexObjectStride` in `navigate.go` accelerates minified homogeneous object
115-
arrays. Pure endpoint arithmetic only runs after **two** consecutive elements
116-
agree on `skipContainer` length (`strideConfirmed`); bulk/direct landings call
117-
`skipContainer` again. That keeps the fast path for real API-list payloads while
118-
preventing false index matches on malformed siblings that merely place a `}` at
119-
the expected offset.
115+
arrays — including elements that embed nested objects/arrays (GitHub-style
116+
`issues[].labels[]`). Pure endpoint arithmetic only runs after **two**
117+
consecutive elements agree on `skipContainer` length (`strideConfirmed`);
118+
bulk/direct landings call `skipContainer` again. Nested structure is allowed;
119+
safety is confirm+landing validation, not a ban on nested object-arrays.
120120

121121
### Sibling multi-key scan
122122

@@ -139,8 +139,8 @@ member walk, using a small active-key set rather than a full multi-path trie.
139139
but need not match any particular library. Fast-path array strides over
140140
homogeneous object elements (FASS equal-size jumps in `navigate.go`) only
141141
activate after two consecutive `skipContainer` lengths match, and direct
142-
landings are re-validated — endpoint shape alone is never enough to accept
143-
an index.
142+
landings are re-validated — nested object-arrays inside elements are allowed;
143+
endpoint shape alone is never enough to accept an index.
144144
6. **The indexed engine is correct at any document size.** The structural index
145145
packs a 29-bit offset, so documents above 512 MiB cannot be indexed; rather
146146
than truncate the index (which would silently mis-answer queries into the

docs/BENCHMARKS.md

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -147,16 +147,17 @@ order-of-magnitude gain on deep/scattered access, queries still zero-allocation.
147147

148148
| Approach | time | allocs |
149149
| --- | --- | --- |
150-
| jseek IndexTape (reused) | **1.54 µs** | 0 B |
151-
| jseek stateless | 47.4 µs | 0 B |
152-
| jseek per-request (pooled) | 55.4 µs | ~0–39 B |
153-
| sonic (arm64 cold) | **15.5 µs** | 328 B |
154-
| gjson GetMany | 60.3 µs | 664 B |
155-
| jsonparser | 154 µs | 0 B |
156-
157-
**Takeaway:** reused IndexTape remains a blowout (**~39× vs gjson**). Cold
158-
stateless still trails sonic on this nested shape (sonic ~3×); per-request
159-
pooled stays competitive with gjson at near-zero alloc.
150+
| jseek IndexTape (reused) | **1.42 µs** | 0 B |
151+
| jseek stateless (FASS on nested issues) | **5.61 µs** | 0 B |
152+
| sonic (arm64 cold) | 14.3 µs | 331 B |
153+
| gjson GetMany | 50.2 µs | 664 B |
154+
| jsonparser | 137 µs | 0 B |
155+
156+
**Takeaway:** FASS now applies to homogeneous object arrays **even when elements
157+
contain nested object-arrays** (safety is the two-element `skipContainer`
158+
confirm + landing re-validate, not a structural ban). Cold stateless GitHub is
159+
**~2.5× faster than sonic arm64** and **~9× faster than gjson**, still 0 B.
160+
IndexTape remains the multi-query blowout (~1.4 µs).
160161

161162
---
162163

@@ -214,13 +215,13 @@ than JIT). Even so:
214215
| Large, shallow 2 fields | **93 ns** / 0 B | ~350 ns / 81 B | **jseek ~3.8×** |
215216
| Large, deep indexed 2 fields | **3.05 µs** / 0 B | 22.7 µs / 90 B | **jseek ~6.5×** (FASS) |
216217
| 12 scattered fields | **86 µs** / 0 B | 119 µs / 572 B | **jseek ~1.4×** |
217-
| GitHub 7 nested fields | 47.4 µs / 0 B | **15.5 µs** / 328 B | **sonic ~3×** |
218+
| GitHub 7 nested fields | **5.61 µs** / 0 B | 14.3 µs / 331 B | **jseek ~2.5×** |
218219

219220
**Updated takeaway (2026-07-16):** FASS + confirmed equal-size strides closed the
220-
old "deep array index loses to sonic" hole on the large homogeneous-users
221-
fixture. The remaining cold loss is **deeply nested, non-uniform** documents
222-
(GitHub-style), where object shapes do not admit fixed strides and sonic's
223-
single pass still pays off.
221+
old deep-array and GitHub cold losses vs sonic arm64 on homogeneous issue/
222+
user arrays — including elements that embed nested object-arrays. Remaining
223+
lazy losses are tiny flat NDJSON field-by-field shapes (Result 6), not nested
224+
API documents.
224225

225226
### Amortized access (IndexTape reused)
226227

@@ -236,7 +237,7 @@ single pass still pays off.
236237
| --- | --- |
237238
| Small / shallow cold | jseek leads, 0 alloc |
238239
| Homogeneous deep array cold | **jseek leads (FASS), 0 alloc** |
239-
| Nested non-uniform cold | sonic still leads ~ |
240+
| Nested homogeneous cold (GitHub issues) | **jseek leads ~2.5×** |
240241
| IndexTape reuse | **jseek 10–46×, 0 alloc** |
241242

242243
---
@@ -250,6 +251,9 @@ single pass still pays off.
250251
deep-index CPU).
251252
3. **`EachKey` array edges use `findIndex` / relative `findIndexObjectStride`**
252253
— multi-path no longer walks every preceding array element with `skipValue`.
254+
4. **Drop the nested-object-array ban on FASS**`labels:[{...}]` inside equal-size
255+
issue objects no longer disables strides; confirm+landing checks keep it safe.
256+
5. **`EachArrayFields`** — single member pass per array element for column harvest.
253257

254258
## Discipline about the numbers
255259

0 commit comments

Comments
 (0)