Skip to content

Commit f5fa792

Browse files
authored
Merge pull request #80: board — PR #79 arc entry (post-merge backfill) + break-even prose fix
board: PR #79 arc entry (post-merge)
2 parents 9cb63e9 + ac0a2a5 commit f5fa792

2 files changed

Lines changed: 48 additions & 1 deletion

File tree

‎.claude/board/PR_ARC_INVENTORY.md‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,53 @@
88
> anti-pattern the imported board rules name. Backfilled below in one
99
> pass rather than left stale; PR #4 onward gets its entry at merge time.
1010
11+
## PR #79 — `hop_cached_vs_gather`: the M1b tile pays off on hop two; the scatter walk is the access-shape question (opened 2026-09-16, merged `9cb63e9`, head `c10029b`)
12+
13+
> **Post-merge backfill, owned (2026-09-16):** this entry was NOT written at
14+
> open. #79 was opened and merged without its arc entry — the same lapse the
15+
> header above records for PRs #1–#3 — and the entry below was reconstructed
16+
> after the merge in PR #80 (Codex P2 on #80 named the omission). Read it as a
17+
> reconstruction, not as a record made with the change; the numbers are the
18+
> ones banked in the probe's module doc and `LATEST_STATE.md` at merge time.
19+
20+
**Added.** `native/lgj-abi/examples/hop_cached_vs_gather.rs` — `lgj_hop`'s
21+
selection three ways at 65 536 rows × 32 facets, every arm asserted
22+
bit-identical on every frontier before timing: `recompute` (the shipped
23+
body: per facet two contiguous `eq_u32` passes + `ternlog<AND3>` + scatter),
24+
`cached` (`sel_f = class_f ∧ struct_f` built once per store generation —
25+
32 × 8 KiB = 256 KiB, the §9 M1b tile keyed `(generation, edge_classid)` —
26+
then one `mask_and` per facet + scatter), `gather` (the scalar row walk on
27+
AosRows, the access-shape baseline, not a candidate). `LATEST_STATE.md`
28+
entry with the full table. No ABI symbol, no Java change.
29+
30+
**Measured** (Xeon 2.10 GHz, `avx512f=true`, release, median of 7, two
31+
runs, every output `black_box`ed inside its timed closure): cache build
32+
843–930 µs; cached **14.4–15.0 / 31.8–33.0 / 96–100 / 159–196 /
33+
903–1 056 µs** across rand7 / rand655 / classid / hop2 / all, against
34+
recompute 728–1 779 µs — break-even **0.9–1.3 hops** at every frontier.
35+
36+
**Locked.** *A store generation's predicates do not change between hops;
37+
re-deriving them per hop is 64 contiguous 256 KiB passes for nothing.* The
38+
scatter is `O(N/64 + selected bits)` per facet (it walks every word of
39+
`selected` before it can know which are empty), not O(frontier) —
40+
CodeRabbit's correction, carried into the doc and the board.
41+
42+
**Deferred.** Invalidation cost (a write to any classid/hi32 lane drops all
43+
32 masks — the registry already does this wholesale for `cached_carving`);
44+
the per-`edge_classid` multiplication of the 256 KiB; and the operator's
45+
actual question — inside the random-access scatter walk, decide a visited
46+
row's 32 facets with one zmm `mask_cmpeq_epi32` per 4 facets (8 loads) or
47+
one xmm compare per facet, instead of the scalar 64-load / 64-branch loop
48+
(`gather_xmm` / `gather_zmm_row`, named, not built).
49+
50+
**Review.** Codex P1 (timed closures returned `()`, outputs unread — LLVM
51+
could drop the scatter stores): fixed, re-measured, every number held.
52+
CodeRabbit: the O(frontier) claim, fixed; docstring-coverage warning
53+
(40 %) on private helpers, not addressed. Bugbot: usage limit, no run.
54+
55+
**Confidence.** HIGH on the numbers (two runs, ranges banked). The
56+
access-granularity arms are unmeasured; the door is named, not opened.
57+
1158
## PR #77 — first CI lint gate: fmt + clippy + rust-test, Rust pinned to 1.98.1 (opened 2026-09-05, head `6d4b1a2`)
1259

1360
**Added.** `.github/workflows/lint.yml` (three jobs: `format`, `clippy`,

‎native/lgj-abi/examples/hop_cached_vs_gather.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
//! | hop2 | 6 943 | 12 125 | 1 019–1 102 | **159–196** | 705–758 | 0.9–1.1 hops |
3333
//! | all | 65 536 | 56 841 | 1 630–1 779 | **903–1 056** | 3 583–3 863 | 1.2–1.3 hops |
3434
//!
35-
//! **The cached tile pays for itself on the second hop** (break-even 1.0–1.4
35+
//! **The cached tile pays for itself on the second hop** (break-even 0.9–1.3
3636
//! hops at every frontier): a store generation's predicates do not change
3737
//! between hops, and the shipped body re-derives them with 64 contiguous
3838
//! 256 KiB passes per hop. What remains per hop is one 8 KiB `mask_and` per

0 commit comments

Comments
 (0)