Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/board/entries/2026-11-03-d-global-where-p0.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
- Folds allocate 0 bytes.
- `where_dr3::LocalSelection` projects bit-identically for two methods. Its `SchemaChanged` on a second method is a coupling, not a correctness need; Scope refuses on its own.

**DuckDB 2.0.0 gate:** 336/336 answers agree; 8 workloads × DR64 / DR256 / multi-tile (16×DR256) × sparse / medium / dense / skewed, at threads 1 and 4.
**DuckDB 2.0.0 gate:** 360/360 answers agree (336 + 24 W8 before-delete checks, added by the follow-up fix); 8 workloads × DR64 / DR256 / multi-tile (16×DR256) × sparse / medium / dense / skewed, at threads 1 and 4.
- Warm best arm: 12/0/0 W/P/L for every workload.
- But DuckDB's fixed per-query cost (~600 µs; 703 µs at 65,536 rows against 12,106 µs at 10M) dominates most warm rows. Those are not claimed as algorithmic wins.
- Work-avoided rows: W4 3-hop on `multi_dense` is 131 µs against 1,500,724 µs; on `multi_skewed` 89 µs against 57,066 µs, where DuckDB builds 3,726,779 join rows for a 1,164 answer.
Expand Down
10 changes: 6 additions & 4 deletions .claude/probes/2026-11-03-d-global-where/REPORT.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

**Status:** MEASURED, lab only.
- Falsifier `falsify`: 21/21 checks. Two guards were disabled to prove they carry weight: F4 and F6 both go red.
- Benchmark against DuckDB 2.0.0: all 336 answer comparisons agree (168 of ours × threads 1 and 4).
- Benchmark against DuckDB 2.0.0: all 360 answer comparisons agree: 168 of ours × threads 1 and 4, plus DuckDB's 12 W8 before-delete answers × 2 threads checked against ours W2. `compare.py` fails on any missing comparison.
- No production change. dt64, Quack, mask-risc and ndarray are used unchanged. No Gather, no new identity table, no witness store, no per-object allocation.

Machine: 4 vCPU Intel Xeon @ 2.10 GHz, rustc 1.99.0 release, DuckDB 2.0.0 (Python).
Expand Down Expand Up @@ -73,7 +73,7 @@ Fixture:
## 3. DuckDB parity gate

**Gate:** parity or better on clearly identified workloads with exact semantic equivalence; every losing workload is recorded with its cause.
- Answers: 336/336 agree.
- Answers: 360/360 agree (336 + 24 W8 before-delete checks against ours W2; coverage enforced in both directions).
- DuckDB runs with its normal optimizer at threads 1 and 4, PREPARE/EXECUTE, 201 warm runs. The Python call floor (median 179 µs, `PREPARE f AS SELECT 1`) is subtracted.
- Ours is single-threaded; "warm" includes the WHERE evaluation and the fold.

Expand All @@ -96,10 +96,12 @@ Rows where DuckDB's own engine time is far above its fixed cost. Warm, µs, Duck
| dataset | workload | ours (best arm) | DuckDB t=1 | DuckDB t=4 | DuckDB intermediate |
|---|---|---|---|---|---|
| multi_dense (503,921 edges) | W4 3-hop distinct | 130.7 (`front`) | 1,500,724 | 418,289 | — |
| multi_skewed (32,648 edges) | W4 3-hop distinct | 89.2 (`front`) | 57,066 | 56,569 | 3,726,779 join rows for a 1,164 answer (`explain/multi_skewed_W4.txt`) |
| multi_skewed (32,648 edges) | W4 3-hop distinct | 89.2 (`front`) | 53,681 | 56,569 | 3,726,779 join rows for a 1,164 answer (`explain/multi_skewed_W4.txt`) |
| multi_dense | W3 2-hop EXISTS | 7.7 (`csr`) | 29,097 | 17,989 | — |
| multi_dense | W6 bag 2-paths (3,382,676) | 119.3 | 22,726 | 11,379 | — |
| multi_skewed | W3 | 7.6 (`csr`) | 6,767 | 6,391 | — |
| multi_skewed | W3 | 7.6 (`csr`) | 5,515 | 6,391 | — |

> **Correction (review, 2026-11-03).** In the first run, `EXPLAIN ANALYZE` executed each query on the measuring connection *before* the cold measurement for `dr256_medium` and `multi_skewed` at t=1, so those 20 cold figures were taken on a warmed query. `bench_duckdb.py` now collects plans after measurement on a separate connection; those 20 records were re-measured and spliced into `duckdb.json` (answers identical). No cold verdict changed; one warm wall verdict moved (multi_skewed W7i `route_each`, WIN → PARITY). The two t=1 figures above are from the re-run. `compare.py` now also fails on any missing comparison in either direction, and checks DuckDB's W8 (W2 before the DELETE) against ours W2.

Here the frontier masks never materialize the join, while DuckDB builds and aggregates the intermediate. This is the intended win: work the query never needed.

Expand Down
17 changes: 13 additions & 4 deletions .claude/probes/2026-11-03-d-global-where/duckdb/bench_duckdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
5. floor_us = median of RUNS `EXECUTE f` for `PREPARE f AS SELECT 1`
(python call + fetch overhead, measured on the same connection).
6. mem_bytes = sum(memory_usage_bytes) FROM duckdb_memory() after the workload.
7. T=1 only, datasets dr256_medium and multi_skewed: `EXPLAIN ANALYZE <sql>`
7. T=1 only, datasets dr256_medium and multi_skewed, after the measurement:
`EXPLAIN ANALYZE <sql>`
text goes to $OUT/explain/<dataset>_<workload>.txt.
8. W8: warm W2, time `DELETE FROM edges WHERE (src, dst) IN (SELECT src, dst
FROM del)` (update_us), warm W2 again. Record "W8" (before delete, with
Expand Down Expand Up @@ -187,13 +188,21 @@ def run_one(out, ds, T, wl, runs, tmpdir):
records = []
if wl != "W8":
sql = SQL[wl]
vals = measure(con, sql, runs, tmpdir)
fl = floor_us(con, runs)
# Plan collection runs AFTER the cold and warm measurement, on its own
# fresh connection: EXPLAIN ANALYZE's execution cannot pre-warm the
# cold figure, and the engine-time profiling mode set on `con` cannot
# turn the plan text into JSON.
if T == 1 and ds in EXPLAIN_DATASETS:
txt = con.execute(f"EXPLAIN ANALYZE {sql}").fetchall()[0][1]
econ = duckdb.connect(":memory:")
econ.execute(f"SET threads={T}")
load(econ, out, ds)
txt = econ.execute(f"EXPLAIN ANALYZE {sql}").fetchall()[0][1]
econ.close()
os.makedirs(os.path.join(out, "explain"), exist_ok=True)
with open(os.path.join(out, "explain", f"{ds}_{wl}.txt"), "w") as f:
f.write(f"-- {sql}\n{txt}\n")
vals = measure(con, sql, runs, tmpdir)
fl = floor_us(con, runs)
records.append(rec(ds, T, wl, vals, fl, None, mem_bytes(con), load_us))
else:
sql = SQL["W2"]
Expand Down
55 changes: 47 additions & 8 deletions .claude/probes/2026-11-03-d-global-where/duckdb/compare.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
writes compare.md, prints it)

Exit status 1 if any answer disagrees (every ours arm vs DuckDB at both thread
counts), with the mismatching rows printed. Exit 2 on unreadable input.
counts) or any comparison is missing in either direction (including a thread
count other than 1 and 4). DuckDB's W8 (W2 before the DELETE) is checked
against ours W2. Exit 2 on unreadable input.

All times are microseconds. Ratios are DuckDB / ours, so > 1 means ours is
faster. Verdict thresholds on the ratio: WIN > 1.10, PARITY 0.90..1.10,
Expand All @@ -25,6 +27,8 @@
import sys
from collections import defaultdict

EXPECTED_THREADS = [1, 4]


def load(path):
try:
Expand Down Expand Up @@ -89,21 +93,50 @@ def main(argv):
summary = defaultdict(lambda: defaultdict(lambda: defaultdict(int)))
# summary[(threads, kind)][workload][verdict]

keys = sorted({(r["dataset"], r["workload"]) for r in dd},
# Coverage is checked in both directions and any gap fails the gate:
# an incomplete run must never print "All compared answers agree".
missing = []
if threads != EXPECTED_THREADS:
missing.append(f"duckdb thread counts {threads}, expected {EXPECTED_THREADS}")
ours_keys = set(ours_by)
dd_keys = {(r["dataset"], r["workload"]) for r in dd}
# DuckDB's W8 is W2 measured on the same connection before the DELETE; ours
# has no separate record for it, so its answer is checked against ours W2.
for ds, wl in sorted(ours_keys - dd_keys):
missing.append(f"no duckdb record for {ds} {wl}")
for ds, wl in sorted(dd_keys):
src = (ds, "W2") if wl == "W8" else (ds, wl)
if src not in ours_by:
missing.append(f"no ours record for {ds} {wl}")
for T in EXPECTED_THREADS:
if (ds, T, wl) not in dd_by:
missing.append(f"no duckdb record for {ds} threads={T} {wl}")
for ds, wl in sorted(dd_keys):
if wl != "W8" or (ds, "W2") not in ours_by:
continue
for T in threads:
d = dd_by.get((ds, T, wl))
if d is None:
continue
for a in ours_by[(ds, "W2")]:
if a.get("answer") is None:
missing.append(f"ours answer missing: {ds} W2 arm={a.get('arm')}")
elif a["answer"] != d["answer"]:
mismatches.append((ds, "W8(before delete) vs ours W2", a.get("arm"), T, a["answer"], d["answer"]))

keys = sorted((k for k in dd_keys if k[1] != "W8"),
key=lambda k: (k[0], wl_order(k[1])))
for ds, wl in keys:
arms = ours_by.get((ds, wl))
if not arms:
notes.append(f"no ours record for {ds} {wl}")
continue
for T in threads:
d = dd_by.get((ds, T, wl))
if d is None:
notes.append(f"no duckdb record for {ds} threads={T} {wl}")
continue
for a in arms:
if a.get("answer") is None:
notes.append(f"ours answer missing: {ds} {wl} arm={a.get('arm')}")
missing.append(f"ours answer missing: {ds} {wl} arm={a.get('arm')}")
elif a["answer"] != d["answer"]:
mismatches.append((ds, wl, a.get("arm"), T, a["answer"], d["answer"]))
floor = d.get("floor_us") or 0.0
Expand Down Expand Up @@ -157,24 +190,30 @@ def main(argv):

if notes:
lines += ["## Notes", ""] + [f"- {n}" for n in notes] + [""]
if missing:
lines += ["## MISSING COMPARISONS", ""] + [f"- {m}" for m in missing] + [""]
if mismatches:
lines += ["## ANSWER MISMATCHES", "", "| dataset | workload | ours arm | duckdb threads | ours answer | duckdb answer |", "|---|---|---|---|---|---|"]
for m in mismatches:
lines.append("| " + " | ".join(str(x) for x in m) + " |")
lines.append("")
else:
lines += ["All compared answers agree.", ""]
elif not missing:
lines += ["All compared answers agree; coverage complete in both directions.", ""]

text = "\n".join(lines)
with open(os.path.join(out, "compare.md"), "w") as f:
f.write(text)
print(text)
if missing:
print(f"\nFAIL: {len(missing)} missing comparison(s)", file=sys.stderr)
for m in missing:
print("MISSING " + m, file=sys.stderr)
if mismatches:
print(f"\nFAIL: {len(mismatches)} answer mismatch(es)", file=sys.stderr)
for m in mismatches:
print("MISMATCH dataset=%s workload=%s arm=%s duckdb_threads=%s ours=%s duckdb=%s" % m, file=sys.stderr)
return 1
return 0
return 1 if missing else 0


if __name__ == "__main__":
Expand Down
Loading
Loading