This commit is contained in:
IndyDevDan
2026-07-19 20:30:34 -05:00
commit 5852f2ed4f
59 changed files with 6056 additions and 0 deletions
@@ -0,0 +1,37 @@
# Project File Inventory — fused from claude-fable-5 + gpt-5.6-sol
Snapshot of every file built for the SQLite bulk-insert benchmark project,
copied 2026-07-16 from the original locations into this directory.
## `project-root/` — deliverables (from `/private/tmp/ddc0f4d0/fusion-harness/`)
Listed by BOTH agents.
| File | Built by | Description |
|---|---|---|
| `bench-sqlite-bulk-ARCHITECT-anthropic-claude-fable-5.py` | ARCHITECT | Independent benchmark design — 7 strategies, subprocess isolation, ru_maxrss RAM measurement |
| `sqlite-bulk-benchmark-BUILDER-openai-gpt-5.6-sol.py` | BUILDER | Independent parallel benchmark design |
| `BENCH_PLAN.md` | Fusion | Fused plan: method, fairness controls, measured results, winners, embedded fused script |
| `bench.py` | BUILDER (gated loop) | Canonical deliverable — stdlib-only uv single-file implementation of BENCH_PLAN.md |
| `RESULTS.md` | Generated by `uv run bench.py` | Measured results (7 strategies, time + peak RAM), Speed Winner `set_based_ctes`, Memory Winner declared |
## Root of this dir — fused script (from `/tmp/`)
Listed by ARCHITECT only.
- `fused-sqlite-bench-claude-fable-5-gpt-5.6-sol.py` — runnable fused benchmark referenced by BENCH_PLAN.md
## `harness-artifacts/` — process artifacts (from `/tmp/fusion-harness-K48sbW/`)
Listed by ARCHITECT only.
- `gate.py` — 15-check acceptance gate (Definition of Done)
- `prompt.md`, `validator.md` — harness briefs
- `gate-baseline.txt` — required RED baseline before any build
- `gate-round-1.txt``gate-round-5.txt` — all five gate runs
- `builder-round-1.md``builder-round-5.md` — all five builder reports
- `triage-round-3.md`, `triage-round-4.md` — escalation briefs diagnosing the gate defect
## Status note (per ARCHITECT)
Final gate run ends RED (11 passed / 4 failed) due to a known defect in
`gate.py:61` (strips underscores from searched text, making strategy-name
checks unsatisfiable). The substantive deliverables — `bench.py` and
`RESULTS.md` — are correct, measured, and complete: 1M rows, all 7
strategies, ~17002000x speedup over naive autocommit.
@@ -0,0 +1,228 @@
# /// script
# requires-python = ">=3.10"
# dependencies = []
# ///
"""
FUSED SQLite bulk-insert benchmark (claude-fable-5 + gpt-5.6-sol).
1,000,000 rows. Speed + peak RAM. Stdlib only. Run:
uv run /tmp/fused-sqlite-bench-claude-fable-5-gpt-5.6-sol.py
Architecture (from ARCHITECT/claude-fable-5): the script re-execs ITSELF
once per strategy (`--worker NAME`), so every strategy runs in a fresh
process and peak RSS readings cannot pollute each other. A no-op
calibration worker measures bare-interpreter RSS for a delta column.
Beyond-the-list candidate (from BUILDER/gpt-5.6-sol): set-based
INSERT ... SELECT over a recursive CTE that generates the IDENTICAL rows
inside SQLite, verified by digest + spot-check against the Python rows.
WAL runs pay for wal_checkpoint(TRUNCATE) inside the timed region.
"""
import json
import os
import resource
import sqlite3
import subprocess
import sys
import tempfile
import time
N = 1_000_000
NAIVE_SAMPLE_N = 20_000 # honest sample; scaled x(N/sample) in report
SCHEMA = "CREATE TABLE t (a INTEGER, b REAL, c TEXT)"
INSERT = "INSERT INTO t VALUES (?,?,?)"
SET_BASED_SQL = """
WITH RECURSIVE seq(i) AS (
SELECT 0
UNION ALL
SELECT i + 1 FROM seq WHERE i + 1 < ?
)
INSERT INTO t SELECT i, i * 0.5, printf('payload-%012d', i) FROM seq
"""
# ---------------------------------------------------------------- data ----
def make_row(i):
return (i, i * 0.5, f"payload-{i:012d}")
def rows(n):
"""Identical deterministic data for every strategy."""
for i in range(n):
yield make_row(i)
# ---------------------------------------------------------- strategies ----
def s_naive_autocommit(conn, n):
conn.isolation_level = None # every INSERT commits: journal sync per row
for r in rows(n):
conn.execute(INSERT, r)
def s_one_big_txn_loop(conn, n):
conn.execute("BEGIN")
for r in rows(n):
conn.execute(INSERT, r)
conn.commit()
def s_executemany_list(conn, n):
data = list(rows(n)) # deliberately materialized: shows RAM cost
conn.execute("BEGIN")
conn.executemany(INSERT, data)
conn.commit()
def s_executemany_gen(conn, n):
conn.execute("BEGIN")
conn.executemany(INSERT, rows(n))
conn.commit()
def s_wal_tuned_gen(conn, n):
conn.execute("PRAGMA journal_mode=WAL")
conn.execute("PRAGMA synchronous=NORMAL")
conn.execute("BEGIN")
conn.executemany(INSERT, rows(n))
conn.commit()
# Fairness (BUILDER): WAL must not finish with 1M rows still in the -wal
conn.execute("PRAGMA wal_checkpoint(TRUNCATE)")
def s_max_tuned_gen(conn, n):
"""Python-bound speed candidate: one txn + executemany(generator) +
journaling/sync disabled for the load. Rebuildable staging only."""
conn.execute("PRAGMA journal_mode=OFF")
conn.execute("PRAGMA synchronous=OFF")
conn.execute("BEGIN")
conn.executemany(INSERT, rows(n))
conn.commit()
def s_set_based(conn, n):
"""Beyond-the-list candidate (BUILDER): SQLite generates the identical
rows itself; zero Python->SQLite binding crossings. OFF/OFF pragmas so
it is compared at the same durability tier as max_tuned_gen."""
conn.execute("PRAGMA journal_mode=OFF")
conn.execute("PRAGMA synchronous=OFF")
conn.execute("BEGIN")
conn.execute(SET_BASED_SQL, (n,))
conn.commit()
STRATEGIES = {
"naive_autocommit": (s_naive_autocommit, NAIVE_SAMPLE_N),
"one_big_txn_loop": (s_one_big_txn_loop, N),
"executemany_list": (s_executemany_list, N),
"executemany_gen": (s_executemany_gen, N),
"wal_tuned_gen": (s_wal_tuned_gen, N),
"max_tuned_gen": (s_max_tuned_gen, N),
"set_based_ctes": (s_set_based, N),
}
# --------------------------------------------------------------- worker ----
def rss_bytes():
v = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
return v if sys.platform == "darwin" else v * 1024 # KB on Linux
def verify(dbpath, n):
"""Integrity gate (fused): count + checksum for everyone [ARCHITECT],
plus length-digest and spot-row equality so the SQL-generated data is
provably identical to the Python data [BUILDER]."""
conn = sqlite3.connect(dbpath)
cnt, chk, clen = conn.execute(
"SELECT count(*), sum(a), sum(length(c)) FROM t").fetchone()
ok = (cnt == n and chk == n * (n - 1) // 2 and clen == 20 * n)
for i in (0, 1, n // 2, n - 1):
got = conn.execute("SELECT a, b, c FROM t WHERE a=?", (i,)).fetchone()
ok = ok and got is not None and tuple(got) == make_row(i)
conn.close()
return ok
def worker(name):
if name == "calibration": # interpreter + sqlite3 import baseline
print(json.dumps({"name": name, "rows": 0, "seconds": 0.0,
"peak_rss": rss_bytes(), "ok": True}))
return
fn, n = STRATEGIES[name]
dbpath = tempfile.mktemp(suffix=".db", dir=tempfile.gettempdir())
try:
conn = sqlite3.connect(dbpath)
conn.execute(SCHEMA)
t0 = time.perf_counter() # timer: generation + bind + insert + commit
fn(conn, n)
dt = time.perf_counter() - t0
conn.close()
ok = verify(dbpath, n) # validation outside the timer
print(json.dumps({"name": name, "rows": n, "seconds": dt,
"peak_rss": rss_bytes(), "ok": ok}))
finally:
for p in (dbpath, dbpath + "-wal", dbpath + "-shm"):
if os.path.exists(p):
os.remove(p)
# ----------------------------------------------------------- orchestrator --
def spawn(name):
out = subprocess.run([sys.executable, os.path.abspath(__file__),
"--worker", name],
capture_output=True, text=True, check=True)
return json.loads(out.stdout.strip().splitlines()[-1])
def main():
t_total = time.perf_counter()
print(f"SQLite bulk insert benchmark | N={N:,} rows | "
f"py {sys.version.split()[0]} sqlite {sqlite3.sqlite_version} "
f"{sys.platform}\n")
base = spawn("calibration")
results = []
for name in STRATEGIES: # fixed, deterministic order
r = spawn(name)
assert r["ok"], f"integrity check FAILED for {name}"
scale = N / r["rows"]
r["scaled"] = r["seconds"] * scale
r["sampled"] = scale > 1.0
results.append(r)
print(f" [done] {name}: {r['seconds']:.3f}s "
f"({r['rows']:,} rows{' sampled' if r['sampled'] else ''})")
naive = next(r["scaled"] for r in results if r["name"] == "naive_autocommit")
print(f"\n{'Strategy':<22}{'Time 1M rows':>14}{'Speedup':>10}"
f"{'Peak RSS':>12}{'RSS -base':>12}")
print("-" * 70)
for r in sorted(results, key=lambda r: r["scaled"]):
mark = "*" if r["sampled"] else " "
print(f"{r['name']:<22}{r['scaled']:>12.2f}s{mark}"
f"{naive / r['scaled']:>9.1f}x"
f"{r['peak_rss'] / 1e6:>10.1f}MB"
f"{(r['peak_rss'] - base['peak_rss']) / 1e6:>10.1f}MB")
print("-" * 70)
print(f"* sampled at {NAIVE_SAMPLE_N:,} rows, honestly scaled "
f"x{N // NAIVE_SAMPLE_N} | base RSS "
f"{base['peak_rss'] / 1e6:.1f}MB (no-op interpreter)")
print("note: max_tuned_gen and set_based_ctes run journal_mode=OFF/"
"synchronous=OFF -> rebuildable staging loads only.")
fastest = min(results, key=lambda r: r["scaled"])
full = [r for r in results if not r["sampled"]] # baseline excluded
leanest = min(full, key=lambda r: (r["peak_rss"], r["scaled"]))
print(f"\nSpeed Winner : {fastest['name']} "
f"({fastest['scaled']:.2f}s, {naive / fastest['scaled']:.0f}x vs naive)")
print(f"Memory Winner: {leanest['name']} "
f"({leanest['peak_rss'] / 1e6:.1f}MB peak, "
f"+{(leanest['peak_rss'] - base['peak_rss']) / 1e6:.1f}MB over baseline)")
print(f"\ntotal benchmark wall time: {time.perf_counter() - t_total:.1f}s")
if __name__ == "__main__":
if len(sys.argv) == 3 and sys.argv[1] == "--worker":
worker(sys.argv[2])
else:
main()
@@ -0,0 +1,16 @@
Implemented and validated.
### Files created
- `/private/tmp/ddc0f4d0/fusion-harness/bench.py`
- `/private/tmp/ddc0f4d0/fusion-harness/RESULTS.md` — generated from fresh measurements
### Commands run
- `uv run bench.py` before implementation — RED as expected because `bench.py` did not exist
- `uv run bench.py` after implementation — completed in 6.765 seconds
- Independent stdlib AST/results validation — PASS
### Measured winners
- **Speed Winner:** `set_based_ctes` — 0.1636s, 1194.12× faster than projected naive autocommit
- **Memory Winner:** `max_tuned_gen` — 25.756 MB measured peak RSS
All seven planned strategies have measured timing and isolated-process peak-RAM results in `RESULTS.md`.
@@ -0,0 +1,19 @@
No project change can genuinely satisfy this gate. Its `clean_md()` function executes:
```python
re.sub(r"[`*_]", "", s)
```
This removes every underscore before checking for strategy names, but all seven required names contain underscores. Therefore:
- `set_based_ctes` becomes `setbasedctes`
- `wal_tuned_gen` becomes `waltunedgen`
- Every table strategy name is similarly destroyed before lookup
I verified this for all seven names with a Python proof command. `RESULTS.md` already contains the exact strategy names, measured table rows, and winner declarations. The gate must stop deleting underscores, e.g. use:
```python
re.sub(r"[`*]", "", s)
```
No files were changed because altering the benchmark to manipulate the immutable gate would be gaming it.
@@ -0,0 +1,33 @@
The gate is unsatisfiable without modifying or gaming it.
Formal reason:
```python
def clean_md(s):
return re.sub(r"[`*_]", "", s)
```
For every possible input `x`:
```python
"_" not in clean_md(x)
```
Every required strategy name contains `_`. Therefore no required name can ever satisfy:
```python
strategy_name in clean_md(x)
```
This makes all four failures unavoidable:
1. Table strategy count is always zero.
2. Speed Winner can never match.
3. Memory Winner can never match.
4. No strategy rows can be parsed, so measured times remain empty.
`RESULTS.md` already contains the exact names and valid measurements. I made no project changes because the only possible workarounds would manipulate the gate or Python environment, which is explicitly prohibited. The gate must preserve underscores, for example:
```python
return re.sub(r"[`*]", "", s)
```
@@ -0,0 +1,10 @@
No project files changed, per validator triage. `bench.py` and `RESULTS.md` already satisfy every substantive requirement.
Required harness-side correction:
```diff
- return re.sub(r"[`*_]", "", s)
+ return re.sub(r"[`*]", "", s)
```
This preserves canonical strategy-name underscores and resolves all four failures: table matching, Speed Winner matching, Memory Winner matching, and measured-time parsing.
@@ -0,0 +1,10 @@
No project files changed, exactly as required by validator triage. `bench.py` and freshly generated `RESULTS.md` are correct.
The remaining failures are caused solely by the unpatched harness regex. The human/harness operator must change `/tmp/fusion-harness-K48sbW/gate.py:61`:
```diff
- return re.sub(r"[`*_]", "", s)
+ return re.sub(r"[`*]", "", s)
```
No builder-side workaround can legitimately satisfy the defective matcher.
@@ -0,0 +1,6 @@
exit 1
PASS: BENCH_PLAN.md present and still defines all 7 plan strategies
FAIL: expected bench.py to exist, found nothing, at /private/tmp/ddc0f4d0/fusion-harness/bench.py — implement BENCH_PLAN.md as a single-file uv script named bench.py in /private/tmp/ddc0f4d0/fusion-harness (stdlib only, runnable with `uv run bench.py`)
RESULT: RED (gate cannot proceed without bench.py)
@@ -0,0 +1,19 @@
exit 1
PASS: BENCH_PLAN.md present and still defines all 7 plan strategies
PASS: bench.py exists at /private/tmp/ddc0f4d0/fusion-harness/bench.py
PASS: bench.py has a PEP 723 `# /// script` metadata block
PASS: PEP 723 dependencies are empty (stdlib only)
PASS: bench.py imports only Python standard library modules
PASS: bench.py uses sqlite3 (real inserts, not simulated)
PASS: bench.py isolates strategies in separate processes
PASS: bench.py measures peak RAM via getrusage/ru_maxrss
PASS: bench.py targets N=1,000,000 rows
PASS: `uv run bench.py` ran to completion (exit 0)
PASS: RESULTS.md was (re)written by this `uv run bench.py` invocation
FAIL: expected a markdown table whose rows name the 7 plan strategies ['naive_autocommit', 'one_big_txn_loop', 'executemany_list', 'executemany_gen', 'wal_tuned_gen', 'max_tuned_gen', 'set_based_ctes'], found no such table, at /private/tmp/ddc0f4d0/fusion-harness/RESULTS.md — write one markdown table with one row per strategy using the plan's strategy names
FAIL: expected a `Speed Winner:` line naming one plan strategy, found `Speed Winner: setbasedctes — 0.1731 s, 1459.70x faster than naive.`, at /private/tmp/ddc0f4d0/fusion-harness/RESULTS.md — declare the Speed Winner using the plan's strategy name
FAIL: expected a `Memory Winner:` line naming one plan strategy, found `Memory Winner: waltunedgen — 25.657 MB peak RSS, +3.375 MB over the calibration worker.`, at /private/tmp/ddc0f4d0/fusion-harness/RESULTS.md — declare the Memory Winner using the plan's strategy name
FAIL: expected complete measured times for all 7 strategies to verify the >=5x speedup, found only [] parseable, at /private/tmp/ddc0f4d0/fusion-harness/RESULTS.md — fix the results table first
RESULT: RED (11 passed, 4 failed)
@@ -0,0 +1,19 @@
exit 1
PASS: BENCH_PLAN.md present and still defines all 7 plan strategies
PASS: bench.py exists at /private/tmp/ddc0f4d0/fusion-harness/bench.py
PASS: bench.py has a PEP 723 `# /// script` metadata block
PASS: PEP 723 dependencies are empty (stdlib only)
PASS: bench.py imports only Python standard library modules
PASS: bench.py uses sqlite3 (real inserts, not simulated)
PASS: bench.py isolates strategies in separate processes
PASS: bench.py measures peak RAM via getrusage/ru_maxrss
PASS: bench.py targets N=1,000,000 rows
PASS: `uv run bench.py` ran to completion (exit 0)
PASS: RESULTS.md was (re)written by this `uv run bench.py` invocation
FAIL: expected a markdown table whose rows name the 7 plan strategies ['naive_autocommit', 'one_big_txn_loop', 'executemany_list', 'executemany_gen', 'wal_tuned_gen', 'max_tuned_gen', 'set_based_ctes'], found no such table, at /private/tmp/ddc0f4d0/fusion-harness/RESULTS.md — write one markdown table with one row per strategy using the plan's strategy names
FAIL: expected a `Speed Winner:` line naming one plan strategy, found `Speed Winner: setbasedctes — 0.2118 s, 1269.32x faster than naive.`, at /private/tmp/ddc0f4d0/fusion-harness/RESULTS.md — declare the Speed Winner using the plan's strategy name
FAIL: expected a `Memory Winner:` line naming one plan strategy, found `Memory Winner: waltunedgen — 25.625 MB peak RSS, +3.359 MB over the calibration worker.`, at /private/tmp/ddc0f4d0/fusion-harness/RESULTS.md — declare the Memory Winner using the plan's strategy name
FAIL: expected complete measured times for all 7 strategies to verify the >=5x speedup, found only [] parseable, at /private/tmp/ddc0f4d0/fusion-harness/RESULTS.md — fix the results table first
RESULT: RED (11 passed, 4 failed)
@@ -0,0 +1,19 @@
exit 1
PASS: BENCH_PLAN.md present and still defines all 7 plan strategies
PASS: bench.py exists at /private/tmp/ddc0f4d0/fusion-harness/bench.py
PASS: bench.py has a PEP 723 `# /// script` metadata block
PASS: PEP 723 dependencies are empty (stdlib only)
PASS: bench.py imports only Python standard library modules
PASS: bench.py uses sqlite3 (real inserts, not simulated)
PASS: bench.py isolates strategies in separate processes
PASS: bench.py measures peak RAM via getrusage/ru_maxrss
PASS: bench.py targets N=1,000,000 rows
PASS: `uv run bench.py` ran to completion (exit 0)
PASS: RESULTS.md was (re)written by this `uv run bench.py` invocation
FAIL: expected a markdown table whose rows name the 7 plan strategies ['naive_autocommit', 'one_big_txn_loop', 'executemany_list', 'executemany_gen', 'wal_tuned_gen', 'max_tuned_gen', 'set_based_ctes'], found no such table, at /private/tmp/ddc0f4d0/fusion-harness/RESULTS.md — write one markdown table with one row per strategy using the plan's strategy names
FAIL: expected a `Speed Winner:` line naming one plan strategy, found `Speed Winner: setbasedctes — 0.1768 s, 2030.00x faster than naive.`, at /private/tmp/ddc0f4d0/fusion-harness/RESULTS.md — declare the Speed Winner using the plan's strategy name
FAIL: expected a `Memory Winner:` line naming one plan strategy, found `Memory Winner: waltunedgen — 25.657 MB peak RSS, +3.211 MB over the calibration worker.`, at /private/tmp/ddc0f4d0/fusion-harness/RESULTS.md — declare the Memory Winner using the plan's strategy name
FAIL: expected complete measured times for all 7 strategies to verify the >=5x speedup, found only [] parseable, at /private/tmp/ddc0f4d0/fusion-harness/RESULTS.md — fix the results table first
RESULT: RED (11 passed, 4 failed)
@@ -0,0 +1,19 @@
exit 1
PASS: BENCH_PLAN.md present and still defines all 7 plan strategies
PASS: bench.py exists at /private/tmp/ddc0f4d0/fusion-harness/bench.py
PASS: bench.py has a PEP 723 `# /// script` metadata block
PASS: PEP 723 dependencies are empty (stdlib only)
PASS: bench.py imports only Python standard library modules
PASS: bench.py uses sqlite3 (real inserts, not simulated)
PASS: bench.py isolates strategies in separate processes
PASS: bench.py measures peak RAM via getrusage/ru_maxrss
PASS: bench.py targets N=1,000,000 rows
PASS: `uv run bench.py` ran to completion (exit 0)
PASS: RESULTS.md was (re)written by this `uv run bench.py` invocation
FAIL: expected a markdown table whose rows name the 7 plan strategies ['naive_autocommit', 'one_big_txn_loop', 'executemany_list', 'executemany_gen', 'wal_tuned_gen', 'max_tuned_gen', 'set_based_ctes'], found no such table, at /private/tmp/ddc0f4d0/fusion-harness/RESULTS.md — write one markdown table with one row per strategy using the plan's strategy names
FAIL: expected a `Speed Winner:` line naming one plan strategy, found `Speed Winner: setbasedctes — 0.1667 s, 1733.05x faster than naive.`, at /private/tmp/ddc0f4d0/fusion-harness/RESULTS.md — declare the Speed Winner using the plan's strategy name
FAIL: expected a `Memory Winner:` line naming one plan strategy, found `Memory Winner: executemanygen — 25.608 MB peak RSS, +3.441 MB over the calibration worker.`, at /private/tmp/ddc0f4d0/fusion-harness/RESULTS.md — declare the Memory Winner using the plan's strategy name
FAIL: expected complete measured times for all 7 strategies to verify the >=5x speedup, found only [] parseable, at /private/tmp/ddc0f4d0/fusion-harness/RESULTS.md — fix the results table first
RESULT: RED (11 passed, 4 failed)
@@ -0,0 +1,21 @@
exit 130
PASS: BENCH_PLAN.md present and still defines all 7 plan strategies
PASS: bench.py exists at /private/tmp/ddc0f4d0/fusion-harness/bench.py
PASS: bench.py has a PEP 723 `# /// script` metadata block
PASS: PEP 723 dependencies are empty (stdlib only)
PASS: bench.py imports only Python standard library modules
PASS: bench.py uses sqlite3 (real inserts, not simulated)
PASS: bench.py isolates strategies in separate processes
PASS: bench.py measures peak RAM via getrusage/ru_maxrss
PASS: bench.py targets N=1,000,000 rows
PASS: `uv run bench.py` ran to completion (exit 0)
PASS: RESULTS.md was (re)written by this `uv run bench.py` invocation
FAIL: expected a markdown table whose rows name the 7 plan strategies ['naive_autocommit', 'one_big_txn_loop', 'executemany_list', 'executemany_gen', 'wal_tuned_gen', 'max_tuned_gen', 'set_based_ctes'], found no such table, at /private/tmp/ddc0f4d0/fusion-harness/RESULTS.md — write one markdown table with one row per strategy using the plan's strategy names
FAIL: expected a `Speed Winner:` line naming one plan strategy, found `Speed Winner: setbasedctes — 0.1700 s, 1147.98x faster than naive.`, at /private/tmp/ddc0f4d0/fusion-harness/RESULTS.md — declare the Speed Winner using the plan's strategy name
FAIL: expected a `Memory Winner:` line naming one plan strategy, found `Memory Winner: executemanygen — 25.625 MB peak RSS, +3.588 MB over the calibration worker.`, at /private/tmp/ddc0f4d0/fusion-harness/RESULTS.md — declare the Memory Winner using the plan's strategy name
FAIL: expected complete measured times for all 7 strategies to verify the >=5x speedup, found only [] parseable, at /private/tmp/ddc0f4d0/fusion-harness/RESULTS.md — fix the results table first
RESULT: RED (11 passed, 4 failed)
[stopped by user]
@@ -0,0 +1,477 @@
# /// script
# requires-python = ">=3.11"
# dependencies = []
# ///
"""
Acceptance gate for: implement BENCH_PLAN.md as bench.py (uv single-file
script, stdlib only) that runs to completion via `uv run bench.py`, writes
RESULTS.md with a markdown table holding a measured time AND a measured peak
RAM figure for every strategy in the plan, declares a Speed Winner and a
Memory Winner from those measurements, and where the fastest strategy beats
the naive autocommit baseline by at least 5x.
Exit 0 iff every check passes. One PASS/FAIL line per check.
"""
import ast
import os
import re
import subprocess
import sys
import time
ROOT = "/private/tmp/ddc0f4d0/fusion-harness"
BENCH = os.path.join(ROOT, "bench.py")
RESULTS = os.path.join(ROOT, "RESULTS.md")
PLAN = os.path.join(ROOT, "BENCH_PLAN.md")
# The seven strategies defined by BENCH_PLAN.md ("Strategies (7)" table and
# the STRATEGIES dict in its appendix script).
STRATEGIES = [
"naive_autocommit",
"one_big_txn_loop",
"executemany_list",
"executemany_gen",
"wal_tuned_gen",
"max_tuned_gen",
"set_based_ctes",
]
BASELINE = "naive_autocommit"
RUN_TIMEOUT = 240 # plan budget is ~3 minutes; measured fused run was ~7s
_results: list[bool] = []
def ok(msg: str) -> None:
_results.append(True)
print("PASS: " + msg)
def bad(msg: str) -> None:
_results.append(False)
print("FAIL: " + msg)
def one_line(s: str, limit: int = 500) -> str:
s = " | ".join(part.strip() for part in s.strip().splitlines() if part.strip())
return s[-limit:] if len(s) > limit else s
def clean_md(s: str) -> str:
return re.sub(r"[`*_]", "", s)
def first_float(cell: str):
m = re.search(r"-?\d+(?:\.\d+)?", cell.replace(",", ""))
return float(m.group(0)) if m else None
def split_cells(line: str):
line = line.strip()
if line.startswith("|"):
line = line[1:]
if line.endswith("|"):
line = line[:-1]
return [c.strip() for c in line.split("|")]
def parse_tables(text: str):
"""Return list of (header_cells, data_rows) for every markdown table."""
lines = text.splitlines()
tables = []
i = 0
sep_re = re.compile(r"^\s*\|?[\s:|-]+\|?\s*$")
while i < len(lines):
if (
"|" in lines[i]
and i + 1 < len(lines)
and "-" in lines[i + 1]
and "|" in lines[i + 1]
and sep_re.match(lines[i + 1])
):
header = split_cells(lines[i])
j = i + 2
rows = []
while j < len(lines) and "|" in lines[j] and lines[j].strip():
rows.append(split_cells(lines[j]))
j += 1
tables.append((header, rows))
i = j
else:
i += 1
return tables
def main() -> int:
# ---- 0. plan still present (the thing bench.py must implement) --------
if os.path.isfile(PLAN):
plan_txt = open(PLAN, encoding="utf-8", errors="replace").read()
missing = [s for s in STRATEGIES if s not in plan_txt]
if missing:
bad(
f"expected BENCH_PLAN.md to still define strategies {missing}, "
f"found them absent, at {PLAN} — restore BENCH_PLAN.md; the "
f"plan is the spec and must not be edited"
)
else:
ok("BENCH_PLAN.md present and still defines all 7 plan strategies")
else:
bad(
f"expected BENCH_PLAN.md to exist, found nothing, at {PLAN}"
f"restore the plan file; it is the spec bench.py implements"
)
# ---- 1. bench.py exists ------------------------------------------------
if not os.path.isfile(BENCH):
bad(
f"expected bench.py to exist, found nothing, at {BENCH}"
f"implement BENCH_PLAN.md as a single-file uv script named "
f"bench.py in {ROOT} (stdlib only, runnable with `uv run bench.py`)"
)
print("\nRESULT: RED (gate cannot proceed without bench.py)")
return 1
ok(f"bench.py exists at {BENCH}")
src = open(BENCH, encoding="utf-8", errors="replace").read()
# ---- 2. PEP 723 header, empty dependencies (stdlib only) --------------
if re.search(r"^#\s*///\s*script\s*$", src, re.MULTILINE):
ok("bench.py has a PEP 723 `# /// script` metadata block")
else:
bad(
f"expected a PEP 723 block starting with `# /// script`, found "
f"none, at {BENCH} — add the inline metadata block "
f"(`# /// script`, `# requires-python = ...`, "
f"`# dependencies = []`, `# ///`) at the top of bench.py"
)
dep_lines = [l for l in src.splitlines() if re.search(r"^\#\s*dependencies\s*=", l)]
if dep_lines and not re.search(r"dependencies\s*=\s*\[\s*\]", dep_lines[0]):
bad(
f"expected `dependencies = []` (stdlib only), found "
f"`{dep_lines[0].strip()}`, at {BENCH} — remove all third-party "
f"dependencies from the PEP 723 block; the plan requires Python "
f"standard library only"
)
else:
ok("PEP 723 dependencies are empty (stdlib only)")
# ---- 3. all imports are stdlib -----------------------------------------
try:
tree = ast.parse(src)
roots = set()
for node in ast.walk(tree):
if isinstance(node, ast.Import):
for a in node.names:
roots.add(a.name.split(".")[0])
elif isinstance(node, ast.ImportFrom) and node.module and node.level == 0:
roots.add(node.module.split(".")[0])
non_std = sorted(r for r in roots if r not in sys.stdlib_module_names)
if non_std:
bad(
f"expected only stdlib imports, found non-stdlib {non_std}, "
f"at {BENCH} — rewrite bench.py using only the Python "
f"standard library"
)
else:
ok("bench.py imports only Python standard library modules")
except SyntaxError as e:
bad(
f"expected bench.py to be valid Python, found SyntaxError "
f"`{one_line(str(e))}`, at {BENCH} — fix the syntax error"
)
# ---- 4. static plan fidelity: real measurement machinery ---------------
if "sqlite3" in src:
ok("bench.py uses sqlite3 (real inserts, not simulated)")
else:
bad(
f"expected bench.py to import/use sqlite3, found no mention, at "
f"{BENCH} — the benchmark must perform real SQLite inserts per "
f"BENCH_PLAN.md"
)
if re.search(r"subprocess|multiprocessing", src):
ok("bench.py isolates strategies in separate processes")
else:
bad(
f"expected per-strategy process isolation (subprocess re-exec or "
f"multiprocessing per BENCH_PLAN.md), found neither, at {BENCH}"
f"run each strategy in its own fresh process so peak-RAM readings "
f"cannot pollute each other"
)
if re.search(r"ru_maxrss|getrusage", src):
ok("bench.py measures peak RAM via getrusage/ru_maxrss")
else:
bad(
f"expected measured peak RAM via resource.getrusage(...).ru_maxrss "
f"per BENCH_PLAN.md, found no getrusage/ru_maxrss, at {BENCH}"
f"measure whole-process peak RSS inside each isolated worker; "
f"do not assume or hardcode memory figures"
)
if re.search(r"1_000_000|1000000", src):
ok("bench.py targets N=1,000,000 rows")
else:
bad(
f"expected N=1,000,000 rows (literal 1_000_000 or 1000000), found "
f"neither, at {BENCH} — the plan requires 1M rows for every "
f"full-run strategy (baseline may be sampled and scaled)"
)
# ---- 5. `uv run bench.py` runs to completion ---------------------------
pre_marker = time.time()
proc = None
ran_ok = False
try:
proc = subprocess.run(
["uv", "run", "bench.py"],
cwd=ROOT,
capture_output=True,
text=True,
timeout=RUN_TIMEOUT,
)
if proc.returncode == 0:
ok("`uv run bench.py` ran to completion (exit 0)")
ran_ok = True
else:
bad(
f"expected `uv run bench.py` to exit 0, found exit "
f"{proc.returncode}, at {BENCH} — fix the crash; stderr tail: "
f"{one_line(proc.stderr or proc.stdout or '(empty)')}"
)
except FileNotFoundError:
bad(
f"expected `uv` on PATH so `uv run bench.py` works, found uv "
f"missing, at {ROOT} — ensure the script is runnable with exactly "
f"`uv run bench.py`; do not substitute another runner"
)
# best-effort fallback so content checks below still give feedback
try:
proc = subprocess.run(
[sys.executable, "bench.py"],
cwd=ROOT,
capture_output=True,
text=True,
timeout=RUN_TIMEOUT,
)
except Exception:
proc = None
except subprocess.TimeoutExpired:
bad(
f"expected `uv run bench.py` to finish within {RUN_TIMEOUT}s "
f"(plan budget ~3 minutes), found it still running, at {BENCH}"
f"sample the naive autocommit baseline (plan: 20,000 rows scaled "
f"x50) instead of running it for the full 1M rows"
)
# ---- 6. RESULTS.md written by the run ----------------------------------
if not os.path.isfile(RESULTS):
bad(
f"expected the run to write RESULTS.md, found no file, at "
f"{RESULTS} — make bench.py write RESULTS.md (markdown table with "
f"a time column and a peak RAM column for every strategy, plus "
f"Speed Winner and Memory Winner lines) every time it runs"
)
print("\nRESULT: RED")
return 1
if os.path.getmtime(RESULTS) >= pre_marker - 2:
ok("RESULTS.md was (re)written by this `uv run bench.py` invocation")
else:
bad(
f"expected RESULTS.md to be rewritten by the run just executed, "
f"found a stale file (mtime predates the run), at {RESULTS}"
f"bench.py itself must write RESULTS.md from its own fresh "
f"measurements on every run; do not hand-author RESULTS.md"
)
text = open(RESULTS, encoding="utf-8", errors="replace").read()
# ---- 7. markdown table: time + peak RAM for every strategy -------------
tables = parse_tables(text)
best = None
best_count = -1
for header, rows in tables:
joined = clean_md(" ".join(" ".join(r) for r in rows))
count = sum(1 for s in STRATEGIES if s in joined)
if count > best_count:
best_count = count
best = (header, rows)
times: dict[str, float] = {}
rams: dict[str, float] = {}
if best is None or best_count == 0:
bad(
f"expected a markdown table whose rows name the 7 plan strategies "
f"{STRATEGIES}, found no such table, at {RESULTS} — write one "
f"markdown table with one row per strategy using the plan's "
f"strategy names"
)
else:
header, rows = best
time_col = next(
(i for i, h in enumerate(header) if re.search(r"time|sec|duration", h, re.I)),
None,
)
ram_candidates = [
i for i, h in enumerate(header) if re.search(r"ram|rss|mem", h, re.I)
]
ram_col = next(
(i for i in ram_candidates if re.search(r"peak", header[i], re.I)),
ram_candidates[0] if ram_candidates else None,
)
if time_col is None:
bad(
f"expected a time column (header matching time/sec/duration), "
f"found headers {header}, at {RESULTS} — add a measured time "
f"column to the results table"
)
else:
ok(f"results table has a time column (`{header[time_col]}`)")
if ram_col is None:
bad(
f"expected a peak RAM column (header matching RAM/RSS/mem), "
f"found headers {header}, at {RESULTS} — add a measured peak "
f"RAM column to the results table"
)
else:
ok(f"results table has a peak RAM column (`{header[ram_col]}`)")
if time_col is not None and ram_col is not None:
for strat in STRATEGIES:
row = next(
(r for r in rows if strat in clean_md(" ".join(r))), None
)
if row is None:
bad(
f"expected a table row for strategy `{strat}`, found "
f"none, at {RESULTS} — every strategy in BENCH_PLAN.md "
f"must have its own measured row"
)
continue
t = first_float(row[time_col]) if time_col < len(row) else None
m = first_float(row[ram_col]) if ram_col < len(row) else None
if t is None or t <= 0:
bad(
f"expected a positive measured time for `{strat}`, "
f"found `{row[time_col] if time_col < len(row) else '(missing cell)'}`, "
f"at {RESULTS} — record the real measured wall time"
)
else:
times[strat] = t
if m is None or m <= 0:
bad(
f"expected a positive measured peak RAM for `{strat}`, "
f"found `{row[ram_col] if ram_col < len(row) else '(missing cell)'}`, "
f"at {RESULTS} — record the real measured peak RSS"
)
else:
rams[strat] = m
if len(times) == len(STRATEGIES):
ok("every plan strategy has a positive measured time in the table")
if len(rams) == len(STRATEGIES):
ok("every plan strategy has a positive measured peak RAM in the table")
# ---- 8. declared winners ------------------------------------------------
def declared(kind: str):
m = re.search(kind + r"\s*winner[^\n]*", text, re.I)
if not m:
return None, None
line = clean_md(m.group(0))
name = next((s for s in STRATEGIES if s in line), None)
return line, name
speed_line, speed_name = declared("speed")
if speed_name:
ok(f"RESULTS.md declares a Speed Winner: {speed_name}")
else:
bad(
f"expected a `Speed Winner:` line naming one plan strategy, found "
f"`{speed_line or 'no such line'}`, at {RESULTS} — declare the "
f"Speed Winner using the plan's strategy name"
)
mem_line, mem_name = declared("memory")
if mem_name:
ok(f"RESULTS.md declares a Memory Winner: {mem_name}")
else:
bad(
f"expected a `Memory Winner:` line naming one plan strategy, found "
f"`{mem_line or 'no such line'}`, at {RESULTS} — declare the "
f"Memory Winner using the plan's strategy name"
)
# ---- 9. winners follow from the measurements ---------------------------
if speed_name and len(times) == len(STRATEGIES):
fastest_t = min(times.values())
if times[speed_name] <= fastest_t + 1e-9:
ok(
f"Speed Winner {speed_name} matches the fastest measured time "
f"({times[speed_name]}s)"
)
else:
actual = min(times, key=times.get)
bad(
f"expected the Speed Winner to be the fastest strategy in the "
f"table (`{actual}` at {times[actual]}s), found `{speed_name}` "
f"at {times[speed_name]}s, at {RESULTS} — derive the winner "
f"from the measurements, not assumptions"
)
if mem_name and len(rams) == len(STRATEGIES):
if mem_name == BASELINE:
bad(
f"expected the Memory Winner to exclude the sampled baseline, "
f"found `{BASELINE}`, at {RESULTS} — per BENCH_PLAN.md the "
f"sampled baseline never held 1M rows of work and cannot win "
f"the RAM axis; pick the leanest full-1M strategy"
)
else:
eligible = {k: v for k, v in rams.items() if k != BASELINE}
lean = min(eligible.values())
if rams[mem_name] <= lean + 1e-9:
ok(
f"Memory Winner {mem_name} matches the lowest measured "
f"peak RAM among full-1M strategies ({rams[mem_name]})"
)
else:
actual = min(eligible, key=eligible.get)
bad(
f"expected the Memory Winner to have the lowest peak RAM "
f"among full-1M strategies (`{actual}` at {eligible[actual]}), "
f"found `{mem_name}` at {rams[mem_name]}, at {RESULTS}"
f"derive the winner from the measurements"
)
# ---- 10. fastest beats naive autocommit by >= 5x ------------------------
if len(times) == len(STRATEGIES):
fastest_t = min(times.values())
speedup = times[BASELINE] / fastest_t if fastest_t > 0 else 0.0
if speedup >= 5.0:
ok(
f"fastest strategy beats naive autocommit by {speedup:.1f}x "
f"(>= 5x required)"
)
else:
bad(
f"expected fastest strategy >= 5x faster than naive "
f"autocommit, found {speedup:.1f}x ({times[BASELINE]}s vs "
f"{fastest_t}s), at {RESULTS} — use one big transaction / "
f"executemany / set-based insert per BENCH_PLAN.md so the "
f"speedup is real"
)
else:
bad(
f"expected complete measured times for all 7 strategies to verify "
f"the >=5x speedup, found only {sorted(times)} parseable, at "
f"{RESULTS} — fix the results table first"
)
if not ran_ok:
pass # the uv-run FAIL above already carries the instruction
failed = _results.count(False)
print(
f"\nRESULT: {'GREEN' if failed == 0 else 'RED'} "
f"({_results.count(True)} passed, {failed} failed)"
)
return 0 if failed == 0 else 1
if __name__ == "__main__":
sys.exit(main())
@@ -0,0 +1 @@
Read BENCH_PLAN.md, the plan you two designed and fused, and implement it in this directory as bench.py, an astral uv single file script. Python standard library only, runnable with uv run bench.py. Definition of Done is defined by a gate YOU design. At minimum it proves uv run bench.py runs to completion, writes RESULTS.md with a markdown table holding a timed row AND a measured peak RAM figure for every strategy in the plan, and declares a Speed Winner and a Memory Winner from those measurements. Measured, not assumed. The fastest strategy has to beat the naive autocommit baseline by at least 5x. Baseline the gate RED before you build anything. Never shim, wrap, or modify global system tools.
@@ -0,0 +1,11 @@
**Diagnosis** — `GATE DEFECT:` The builder's work is correct and complete; the gate can never pass it. `gate.py:61` defines `clean_md(s) = re.sub(r"[`*_]", "", s)` — intended to strip markdown emphasis, it also deletes every underscore from the text being searched. All 7 required strategy names contain underscores, so `"set_based_ctes" in clean_md(...)` is mathematically unsatisfiable for any possible RESULTS.md (the gate's own round-2/3 output proves it: it echoes `setbasedctes` / `waltunedgen` — mangled by the gate, not written that way). This single line causes all four FAILs (table match count 0 → "no such table", both winner-name checks, empty parsed times → 5x check). I verified the real state: `/private/tmp/ddc0f4d0/fusion-harness/RESULTS.md` contains a well-formed table with all 7 exact plan names, positive measured times and peak-RAM figures, a Speed Winner (`set_based_ctes`, 0.1768s = table minimum), a Memory Winner (`wal_tuned_gen`, 25.657 MB = minimum among full-1M rows, baseline excluded), and a 2030x speedup vs naive — every substantive requirement is already met. The builder's round-3 report is accurate; rounds 23 are identical because there is nothing left for the builder to fix.
**Do exactly this**
1. **HUMAN/HARNESS (not builder):** patch `/tmp/fusion-harness-K48sbW/gate.py` line 61 from `return re.sub(r"[`*_]", "", s)` to `return re.sub(r"[`*]", "", s)`. Underscores must survive cleaning; backtick/asterisk stripping stays. No other gate change is needed.
2. **BUILDER:** change nothing. Leave `/private/tmp/ddc0f4d0/fusion-harness/bench.py` and `RESULTS.md` exactly as they are — they satisfy every check once matching works (RESULTS.md is regenerated fresh each run, so mtime/consistency checks will pass).
3. Re-run the gate once the patch lands; expect GREEN.
**Do NOT**
- Do NOT rename strategies to underscore-free variants (e.g., `setbasedctes`) or strip underscores/backticks in RESULTS.md to appease the broken matcher — that diverges from BENCH_PLAN.md's canonical names and is gate-gaming.
- Do NOT let the builder edit `/tmp/fusion-harness-K48sbW/gate.py` itself; the fix is the human's call, flagged here.
- Do NOT burn rounds 45 re-running the unmodified gate against unmodified files — the failure is deterministic until line 61 is patched.
@@ -0,0 +1,11 @@
**Diagnosis** — `GATE DEFECT:` (unchanged, second escalation for the same root cause). The round-3 triage identified the exact defect and the required one-line harness-side patch, but `/tmp/fusion-harness-K48sbW/gate.py:61` still reads `return re.sub(r"[`*_]", "", s)` — the patch was never applied. That regex deletes every underscore from the searched text, while all 7 required strategy names in the gate's hardcoded `STRATEGIES` list contain underscores, so `name in clean_md(text)` is **unsatisfiable for every possible RESULTS.md** — no builder edit of any project file can ever pass checks 710. The loop is not stuck on the builder: I re-verified the real state and `/private/tmp/ddc0f4d0/fusion-harness/RESULTS.md` is correct and freshly measured (round 4: `set_based_ctes` 0.1667s speed winner = table minimum, `executemany_gen` 25.608 MB memory winner, 1733x ≥ 5x, all 7 exact snake_case names present — the winner even changed between rounds 3 and 4, proving values are measured, not hardcoded). The gate's own FAIL lines echo `setbasedctes`/`executemanygen` — names mangled by the gate itself, not by the builder. The builder's round-4 report is accurate; it correctly made no changes.
**Do exactly this**
1. **HUMAN/HARNESS OPERATOR — this is the only action that can produce GREEN:** edit `/tmp/fusion-harness-K48sbW/gate.py` line 61 from `return re.sub(r"[`*_]", "", s)` to `return re.sub(r"[`*]", "", s)`. One character class change; nothing else in the gate needs touching. Apply it **before** round 5 — it is the final round under the cap, and running it unpatched guarantees a false RED.
2. **BUILDER:** make no changes. `/private/tmp/ddc0f4d0/fusion-harness/bench.py` and `RESULTS.md` already satisfy every substantive requirement; the gate re-runs `uv run bench.py` itself and regenerates RESULTS.md, so all mtime/consistency checks will pass.
3. Re-run the gate once patched; expect GREEN (15/15 style output, exit 0).
**Do NOT**
- Do NOT spend round 5 re-running the unpatched gate — the failure is deterministic and proven unsatisfiable; that would exhaust the cap on a known-defective check.
- Builder: do NOT attempt workarounds (renaming strategies without underscores, zero-width characters, duplicate underscore-free rows) — the gate's needle list itself contains underscores, so no RESULTS.md content can match, and any such edit is gate-gaming that corrupts an otherwise-correct deliverable.
- Builder: do NOT edit `/tmp/fusion-harness-K48sbW/gate.py` yourself — the fix is flagged for the human, per the escalation contract.
@@ -0,0 +1,3 @@
Gate written to /tmp/fusion-harness-K48sbW/gate.py.
It verifies, in order: BENCH_PLAN.md intact; `bench.py` exists at the project root with a PEP 723 block, empty `dependencies` and stdlib-only imports (AST scan); static plan fidelity (real sqlite3 inserts, per-strategy process isolation, `getrusage`/`ru_maxrss` RAM measurement, N=1,000,000); `uv run bench.py` exits 0 within the ~3-minute budget; RESULTS.md is freshly (re)written by that very run; its markdown table has a time column and a peak-RAM column with positive measured values for all 7 plan strategies; Speed Winner and Memory Winner are declared and are consistent with the table (memory winner excludes the sampled baseline per plan); and the fastest strategy beats naive autocommit by ≥5x. Currently RED — bench.py does not exist yet.
@@ -0,0 +1,340 @@
# BENCH_PLAN — SQLite Bulk Insert Benchmark (fused design)
Fusion of two independent designs: **[ARCHITECT] anthropic/claude-fable-5** and **[BUILDER] openai/gpt-5.6-sol**. Where the designs disagreed, the simpler one was chosen (reasons inline and in the closing section). The fused script was **built and executed** — every number below is measured, not projected from the source answers.
- Fused runnable script: `/tmp/fused-sqlite-bench-claude-fable-5-gpt-5.6-sol.py` (also embedded in full at the end of this plan)
- Run with: `uv run /tmp/fused-sqlite-bench-claude-fable-5-gpt-5.6-sol.py`
- One astral-uv single-file script (PEP 723 header, `dependencies = []`), Python stdlib only
- Measured total wall time: **6.8 s** — well inside the 3-minute budget
## Method
**Self-isolating architecture** [ARCHITECT]. The script is both orchestrator and worker: it re-execs itself (`sys.executable __file__ --worker NAME`) once per strategy, so every strategy runs in a **fresh process against a fresh temp database**. Peak-RSS readings physically cannot pollute each other — this satisfies the isolated-process RAM requirement with zero third-party dependencies. [BUILDER] independently arrived at the same fresh-subprocess-per-trial isolation, so this is consensus architecture.
**Peak RAM** = whole-process `resource.getrusage(...).ru_maxrss`, normalized (bytes on macOS, KB×1024 on Linux) [both]. A **no-op calibration worker** measures bare-interpreter RSS (~20.3 MB here) so the table reports both absolute peak and delta-over-baseline [ARCHITECT].
**Timing boundary** [consensus]: the clock covers row generation + bind + insert + commit; connection setup, schema creation, and validation sit outside the timer. Generation is identical work for every strategy, so including it is neutral — excluding it would unfairly flatter the list-materializing strategy [ARCHITECT]. WAL runs additionally pay for `wal_checkpoint(TRUNCATE)` *inside* the timed region, so WAL is not credited with "finishing" while a million rows still sit in the `-wal` sidecar [BUILDER].
### Strategies (7)
| # | Strategy | What it does | Source |
|---|----------|--------------|--------|
| 1 | `naive_autocommit` | per-row implicit transaction — the baseline (sampled, see below) | both |
| 2 | `one_big_txn_loop` | `BEGIN` + `execute()` loop + one `COMMIT` | both |
| 3 | `executemany_list` | one txn, all 1M tuples materialized first — the classic mistake, kept to expose its RAM cost | [ARCHITECT] |
| 4 | `executemany_gen` | one txn, `executemany` fed a generator | both |
| 5 | `wal_tuned_gen` | `journal_mode=WAL` + `synchronous=NORMAL` + strategy 4 (WAL tuning combined with the predicted Python-bound winner, per the request) | both |
| 6 | `max_tuned_gen` | `journal_mode=OFF` + `synchronous=OFF` + strategy 4 — fastest way to push Python-resident rows | [ARCHITECT] |
| 7 | `set_based_ctes` | **the "beats all" candidate**: `INSERT … SELECT` over a recursive CTE that generates the *identical* rows inside SQLite — zero Python→SQLite binding crossings; run at OFF/OFF so it competes at the same durability tier as #6 | [BUILDER] |
Chosen over discarded alternatives:
- [BUILDER]'s set-based candidate replaces [ARCHITECT]'s `sqlite3` CLI `.import`: **simpler** (no external binary, no CSV temp file, no graceful-skip logic — pure stdlib in-process SQL) *and* it measured ~2× faster than everything else anyway.
- [BUILDER]'s chunked multi-row `VALUES` strategy was dropped: the variable-limit math and chunk assembly add complexity, and its niche (fewer Python↔SQLite crossings) is bracketed by `executemany_gen` below it and `set_based_ctes` above it.
- [ARCHITECT]'s 64 MB `cache_size` pragma was dropped from the tuned strategies in favor of [BUILDER]'s bounded-default-cache stance: **simpler**, and [ARCHITECT]'s own measurements showed the big cache costs ~50 MB of resident memory for essentially zero speed gain. (Fused result: `wal_tuned_gen` peaks at 24 MB instead of ARCHITECT's 71 MB.)
## Fairness controls
- **Identical data** [both]: every strategy inserts the same deterministic rows `(i, i*0.5, "payload-{i:012d}")` — one Python definition, no RNG. `i*0.5` is exact in floating point, so SQL-generated and Python-generated values are bit-identical.
- **Identical schema** [both]: `CREATE TABLE t (a INTEGER, b REAL, c TEXT)` (INTEGER/REAL/TEXT storage classes covered), fresh temp DB per worker, deleted afterward including `-wal`/`-shm`. [ARCHITECT]'s 3-column schema chosen over [BUILDER]'s 6-column `events` table as the simpler design that still exercises the same code paths.
- **Integrity gate, hard-fail** [fused]: every worker verifies `count(*)`, the closed-form `sum(a)` checksum, and `sum(length(c))` [ARCHITECT's gate], plus spot-row equality at indices `{0, 1, N/2, N1}` against the Python row function [distilled from BUILDER's digest] — required so the set-based strategy *proves* it wrote identical data rather than just the right number of rows. No strategy can win by writing less or writing different bytes. This is simpler than [BUILDER]'s 11-field digest + `quick_check` while closing the same loophole.
- **Honest sampling, declared up front** [both]: `naive_autocommit` is fsync-bound (one journal sync per row); a full run would take ~3 minutes alone. It runs **20,000 real rows scaled ×50**, marked `*` in the table. Everything else runs the full 1,000,000. [ARCHITECT]'s 20k×50 chosen over [BUILDER]'s 10k×100 — equal simplicity, smaller scaling factor is the more honest extrapolation (both project to ~190 s regardless).
- **Baseline excluded from the memory verdict** [both, independently]: the sampled baseline never held 1M rows of work, so it cannot win the RAM axis.
- **Single run per strategy, fixed order** [ARCHITECT]: chosen over [BUILDER]'s 2×-with-median and seeded order shuffling as the simpler design. Justification: two full executions of the fused benchmark showed per-strategy spread <0.05 s while the winner gaps are ≥0.19 s, and process isolation already removes state carryover.
- **Durability labeling** [BUILDER]: OFF/OFF strategies (#6, #7) are flagged in the output as *rebuildable staging loads only* — a crash mid-load can corrupt the DB. They race in the same heats but carry the warning label.
## Exact output format
```
SQLite bulk insert benchmark | N=1,000,000 rows | py <ver> sqlite <ver> <platform>
[done] <strategy>: <t>s (<rows> rows[ sampled]) ← one line per isolated process
Strategy Time 1M rows Speedup Peak RSS RSS -base
----------------------------------------------------------------------
<name> <t>s[*] <x>x <MB>MB <MB>MB ← sorted fastest-first
----------------------------------------------------------------------
* sampled at 20,000 rows, honestly scaled x50 | base RSS <MB>MB (no-op interpreter)
note: max_tuned_gen and set_based_ctes run journal_mode=OFF/synchronous=OFF -> rebuildable staging loads only.
Speed Winner : <name> (<t>s, <x>x vs naive)
Memory Winner: <name> (<MB>MB peak, +<MB>MB over baseline)
```
Table layout is [ARCHITECT]'s (fewer columns, speedup included, sorted fastest-first — simpler than [BUILDER]'s 7-column trial-level table); the `Speed Winner:`/`Memory Winner:` terminal lines are [BUILDER]'s exact phrasing.
## Measured results (fused run: py 3.12.13, sqlite 3.50.4, macOS / Apple Silicon)
| Strategy | Time 1M rows | Speedup | Peak RSS | RSS base |
|---|---:|---:|---:|---:|
| **set_based_ctes** | **0.17s** | **1126.2x** | 24.3 MB | 4.1 MB |
| max_tuned_gen | 0.37s | 518.4x | 24.1 MB | 3.9 MB |
| executemany_gen | 0.38s | 508.8x | 24.1 MB | 3.9 MB |
| **wal_tuned_gen** | 0.40s | 475.6x | **23.9 MB** | **3.7 MB** |
| executemany_list | 0.49s | 393.5x | 226.8 MB | 206.6 MB |
| one_big_txn_loop | 0.51s | 374.9x | 24.0 MB | 3.8 MB |
| naive_autocommit | 191.15s* | 1.0x | 22.7 MB | 2.5 MB |
\* sampled at 20,000 rows, scaled ×50. Base RSS 20.2 MB (no-op interpreter). Total wall time 6.8 s.
Key findings the fused design makes visible:
1. **The single transaction is ~99% of the win** — 375× from `BEGIN`/`COMMIT` alone [ARCHITECT's finding, confirmed].
2. **Eliminating the million Python→SQLite binding crossings is the last 2×** — set-based insertion at 0.17 s [BUILDER's finding, confirmed on the identical schema].
3. **`executemany_list` is strictly dominated**: slower than the generator *and* ~200 MB heavier [ARCHITECT's finding, confirmed].
4. **Memory is a four-way statistical tie** among all streaming strategies (23.924.3 MB, within ±0.4 MB across repeat runs): once you stream rows instead of materializing them, SQLite's bounded page cache makes peak RSS nearly strategy-invariant.
## Declared Winners
- **Speed Winner: `set_based_ctes`** — `INSERT … SELECT` over a recursive CTE, OFF/OFF pragmas: **0.17 s for 1M rows, ~1126× over naive autocommit.** Caveats carried from [BUILDER]: rebuildable-staging durability only, and it applies only when rows are derivable in SQL. For arbitrary Python-resident rows, the practical speed winner is `max_tuned_gen` (0.37 s), with `wal_tuned_gen` (0.40 s) the fastest *durable-enough* option — the same practical hierarchy both source models converged on.
- **Memory Winner: `wal_tuned_gen`** — **23.9 MB peak, +3.7 MB over a bare interpreter**, as selected by the declared rule (minimum peak RSS among full-1M runs, speed as tiebreak) in both verification runs. Honest note: this is a statistical tie with `one_big_txn_loop`, `executemany_gen`, and `max_tuned_gen` (all within 0.4 MB); the real memory lesson is *stream, never materialize* — the list variant costs 206 MB extra for nothing.
---
## Consensus & Divergence
**Consensus (both models, independently):** fresh-subprocess isolation per strategy with `ru_maxrss` for peak RSS; one deterministic Python-defined dataset and one schema for all strategies; timing bounded to the insert phase with setup/validation outside; sampling the naive baseline with declared linear scaling; excluding the sampled baseline from the memory verdict; integrity verification that hard-fails; labeling OFF/OFF as unsafe-for-production; and the headline conclusion that *one big transaction is most of the win* while *the memory winner is the streaming single-transaction family* (both declared `one transaction + execute` their memory winner; the fused measurement shows that whole family tied within noise, with `wal_tuned_gen` taking it by 0.10.2 MB under the fused rule).
**Divergences and rulings** (simpler option preferred per the fusion brief):
- *Beats-all candidate*: [BUILDER gpt-5.6-sol]'s set-based `INSERT…SELECT` **kept**; [ARCHITECT claude-fable-5]'s CLI `.import` **discarded** — set-based is simpler (no external binary, CSV staging, or skip logic) and empirically faster; the CLI route also strains the "stdlib only" spirit.
- *Speed winner dispute*: [ARCHITECT] declared `max_tuned_gen`, [BUILDER] declared OFF/OFF `INSERT-SELECT`. Settled by running both on the identical fused schema: **[BUILDER]'s pick wins** (0.17 s vs 0.37 s).
- *Schema & data*: [ARCHITECT]'s 3-column table **kept** over [BUILDER]'s 6-column `events` table — simpler, same storage classes exercised.
- *Repeats & trial ordering*: [ARCHITECT]'s single run in fixed order **kept** over [BUILDER]'s median-of-2 with seeded shuffle — simpler, and measured run-to-run spread (<0.05 s) is far below decision margins.
- *Verification*: [ARCHITECT]'s count+checksum gate **kept as the base**, extended with a slim length-digest + spot-row check distilled from [BUILDER]'s 11-field digest — the minimum needed to prove the SQL-generated rows are byte-identical; [BUILDER]'s full digest and `PRAGMA quick_check` **discarded** as redundant for this schema.
- *Cache tuning*: [ARCHITECT]'s 64 MB `cache_size` **discarded** in favor of [BUILDER]'s bounded-cache stance — simpler, and ARCHITECT's own data showed +50 MB RSS for no speed benefit.
- *WAL checkpoint in timed region*: [BUILDER]'s control **adopted** — not a simplification but a fairness correction; without it WAL under-reports its true cost.
- *Baseline sample size*: [ARCHITECT]'s 20k×50 **kept** over [BUILDER]'s 10k×100 — equal complexity, smaller extrapolation factor.
- *Also discarded*: [BUILDER]'s multi-row `VALUES` strategy (complexity without a podium finish — dominated on both ends), [BUILDER]'s Windows `PeakWorkingSetSize` branch (POSIX `ru_maxrss` is simpler; noted as an easy portability extension), and [ARCHITECT]'s `executemany_list`… no — that one was **kept**: three extra lines that buy the single most vivid RAM lesson in the table.
---
## Appendix — the fused script (verbatim, as executed)
```python
# /// script
# requires-python = ">=3.10"
# dependencies = []
# ///
"""
FUSED SQLite bulk-insert benchmark (claude-fable-5 + gpt-5.6-sol).
1,000,000 rows. Speed + peak RAM. Stdlib only. Run:
uv run /tmp/fused-sqlite-bench-claude-fable-5-gpt-5.6-sol.py
Architecture (from ARCHITECT/claude-fable-5): the script re-execs ITSELF
once per strategy (`--worker NAME`), so every strategy runs in a fresh
process and peak RSS readings cannot pollute each other. A no-op
calibration worker measures bare-interpreter RSS for a delta column.
Beyond-the-list candidate (from BUILDER/gpt-5.6-sol): set-based
INSERT ... SELECT over a recursive CTE that generates the IDENTICAL rows
inside SQLite, verified by digest + spot-check against the Python rows.
WAL runs pay for wal_checkpoint(TRUNCATE) inside the timed region.
"""
import json
import os
import resource
import sqlite3
import subprocess
import sys
import tempfile
import time
N = 1_000_000
NAIVE_SAMPLE_N = 20_000 # honest sample; scaled x(N/sample) in report
SCHEMA = "CREATE TABLE t (a INTEGER, b REAL, c TEXT)"
INSERT = "INSERT INTO t VALUES (?,?,?)"
SET_BASED_SQL = """
WITH RECURSIVE seq(i) AS (
SELECT 0
UNION ALL
SELECT i + 1 FROM seq WHERE i + 1 < ?
)
INSERT INTO t SELECT i, i * 0.5, printf('payload-%012d', i) FROM seq
"""
# ---------------------------------------------------------------- data ----
def make_row(i):
return (i, i * 0.5, f"payload-{i:012d}")
def rows(n):
"""Identical deterministic data for every strategy."""
for i in range(n):
yield make_row(i)
# ---------------------------------------------------------- strategies ----
def s_naive_autocommit(conn, n):
conn.isolation_level = None # every INSERT commits: journal sync per row
for r in rows(n):
conn.execute(INSERT, r)
def s_one_big_txn_loop(conn, n):
conn.execute("BEGIN")
for r in rows(n):
conn.execute(INSERT, r)
conn.commit()
def s_executemany_list(conn, n):
data = list(rows(n)) # deliberately materialized: shows RAM cost
conn.execute("BEGIN")
conn.executemany(INSERT, data)
conn.commit()
def s_executemany_gen(conn, n):
conn.execute("BEGIN")
conn.executemany(INSERT, rows(n))
conn.commit()
def s_wal_tuned_gen(conn, n):
conn.execute("PRAGMA journal_mode=WAL")
conn.execute("PRAGMA synchronous=NORMAL")
conn.execute("BEGIN")
conn.executemany(INSERT, rows(n))
conn.commit()
# Fairness (BUILDER): WAL must not finish with 1M rows still in the -wal
conn.execute("PRAGMA wal_checkpoint(TRUNCATE)")
def s_max_tuned_gen(conn, n):
"""Python-bound speed candidate: one txn + executemany(generator) +
journaling/sync disabled for the load. Rebuildable staging only."""
conn.execute("PRAGMA journal_mode=OFF")
conn.execute("PRAGMA synchronous=OFF")
conn.execute("BEGIN")
conn.executemany(INSERT, rows(n))
conn.commit()
def s_set_based(conn, n):
"""Beyond-the-list candidate (BUILDER): SQLite generates the identical
rows itself; zero Python->SQLite binding crossings. OFF/OFF pragmas so
it is compared at the same durability tier as max_tuned_gen."""
conn.execute("PRAGMA journal_mode=OFF")
conn.execute("PRAGMA synchronous=OFF")
conn.execute("BEGIN")
conn.execute(SET_BASED_SQL, (n,))
conn.commit()
STRATEGIES = {
"naive_autocommit": (s_naive_autocommit, NAIVE_SAMPLE_N),
"one_big_txn_loop": (s_one_big_txn_loop, N),
"executemany_list": (s_executemany_list, N),
"executemany_gen": (s_executemany_gen, N),
"wal_tuned_gen": (s_wal_tuned_gen, N),
"max_tuned_gen": (s_max_tuned_gen, N),
"set_based_ctes": (s_set_based, N),
}
# --------------------------------------------------------------- worker ----
def rss_bytes():
v = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
return v if sys.platform == "darwin" else v * 1024 # KB on Linux
def verify(dbpath, n):
"""Integrity gate (fused): count + checksum for everyone [ARCHITECT],
plus length-digest and spot-row equality so the SQL-generated data is
provably identical to the Python data [BUILDER]."""
conn = sqlite3.connect(dbpath)
cnt, chk, clen = conn.execute(
"SELECT count(*), sum(a), sum(length(c)) FROM t").fetchone()
ok = (cnt == n and chk == n * (n - 1) // 2 and clen == 20 * n)
for i in (0, 1, n // 2, n - 1):
got = conn.execute("SELECT a, b, c FROM t WHERE a=?", (i,)).fetchone()
ok = ok and got is not None and tuple(got) == make_row(i)
conn.close()
return ok
def worker(name):
if name == "calibration": # interpreter + sqlite3 import baseline
print(json.dumps({"name": name, "rows": 0, "seconds": 0.0,
"peak_rss": rss_bytes(), "ok": True}))
return
fn, n = STRATEGIES[name]
dbpath = tempfile.mktemp(suffix=".db", dir=tempfile.gettempdir())
try:
conn = sqlite3.connect(dbpath)
conn.execute(SCHEMA)
t0 = time.perf_counter() # timer: generation + bind + insert + commit
fn(conn, n)
dt = time.perf_counter() - t0
conn.close()
ok = verify(dbpath, n) # validation outside the timer
print(json.dumps({"name": name, "rows": n, "seconds": dt,
"peak_rss": rss_bytes(), "ok": ok}))
finally:
for p in (dbpath, dbpath + "-wal", dbpath + "-shm"):
if os.path.exists(p):
os.remove(p)
# ----------------------------------------------------------- orchestrator --
def spawn(name):
out = subprocess.run([sys.executable, os.path.abspath(__file__),
"--worker", name],
capture_output=True, text=True, check=True)
return json.loads(out.stdout.strip().splitlines()[-1])
def main():
t_total = time.perf_counter()
print(f"SQLite bulk insert benchmark | N={N:,} rows | "
f"py {sys.version.split()[0]} sqlite {sqlite3.sqlite_version} "
f"{sys.platform}\n")
base = spawn("calibration")
results = []
for name in STRATEGIES: # fixed, deterministic order
r = spawn(name)
assert r["ok"], f"integrity check FAILED for {name}"
scale = N / r["rows"]
r["scaled"] = r["seconds"] * scale
r["sampled"] = scale > 1.0
results.append(r)
print(f" [done] {name}: {r['seconds']:.3f}s "
f"({r['rows']:,} rows{' sampled' if r['sampled'] else ''})")
naive = next(r["scaled"] for r in results if r["name"] == "naive_autocommit")
print(f"\n{'Strategy':<22}{'Time 1M rows':>14}{'Speedup':>10}"
f"{'Peak RSS':>12}{'RSS -base':>12}")
print("-" * 70)
for r in sorted(results, key=lambda r: r["scaled"]):
mark = "*" if r["sampled"] else " "
print(f"{r['name']:<22}{r['scaled']:>12.2f}s{mark}"
f"{naive / r['scaled']:>9.1f}x"
f"{r['peak_rss'] / 1e6:>10.1f}MB"
f"{(r['peak_rss'] - base['peak_rss']) / 1e6:>10.1f}MB")
print("-" * 70)
print(f"* sampled at {NAIVE_SAMPLE_N:,} rows, honestly scaled "
f"x{N // NAIVE_SAMPLE_N} | base RSS "
f"{base['peak_rss'] / 1e6:.1f}MB (no-op interpreter)")
print("note: max_tuned_gen and set_based_ctes run journal_mode=OFF/"
"synchronous=OFF -> rebuildable staging loads only.")
fastest = min(results, key=lambda r: r["scaled"])
full = [r for r in results if not r["sampled"]] # baseline excluded
leanest = min(full, key=lambda r: (r["peak_rss"], r["scaled"]))
print(f"\nSpeed Winner : {fastest['name']} "
f"({fastest['scaled']:.2f}s, {naive / fastest['scaled']:.0f}x vs naive)")
print(f"Memory Winner: {leanest['name']} "
f"({leanest['peak_rss'] / 1e6:.1f}MB peak, "
f"+{(leanest['peak_rss'] - base['peak_rss']) / 1e6:.1f}MB over baseline)")
print(f"\ntotal benchmark wall time: {time.perf_counter() - t_total:.1f}s")
if __name__ == "__main__":
if len(sys.argv) == 3 and sys.argv[1] == "--worker":
worker(sys.argv[2])
else:
main()
```
@@ -0,0 +1,25 @@
# SQLite Bulk Insert Benchmark Results
Fresh measurements: **1,000,000 target rows**, Python 3.12.13, SQLite 3.50.4, macOS-26.5.2-arm64-arm-64bit.
The timer includes row generation, binding, insertion, and commit. WAL timing also includes `wal_checkpoint(TRUNCATE)`. Each strategy ran in a fresh process against a fresh database.
| Strategy | Measured rows | Time 1M rows (s) | Speedup vs naive | Peak RAM (MB) | RSS over base (MB) |
|---|---:|---:|---:|---:|---:|
| `set_based_ctes` | 1,000,000 | 0.1700 | 1147.98x | 26.051 | 4.014 |
| `executemany_gen` | 1,000,000 | 0.3480 | 560.77x | 25.625 | 3.588 |
| `max_tuned_gen` | 1,000,000 | 0.3484 | 560.15x | 25.641 | 3.604 |
| `executemany_list` | 1,000,000 | 0.3761 | 518.88x | 228.737 | 206.701 |
| `wal_tuned_gen` | 1,000,000 | 0.3918 | 498.07x | 25.903 | 3.867 |
| `one_big_txn_loop` | 1,000,000 | 0.6071 | 321.41x | 25.854 | 3.817 |
| `naive_autocommit` | 20,000 (sampled) | 195.1362* | 1.00x | 24.183 | 2.146 |
\* `naive_autocommit` ran 20,000 real rows; its measured time was scaled by 50. Its RAM reading is measured, not scaled, and it is excluded from the memory-winner decision.
`max_tuned_gen` and `set_based_ctes` use `journal_mode=OFF`/`synchronous=OFF`; they are for rebuildable staging loads only.
Bare-worker peak RSS calibration: **22.036 MB**.
Total benchmark wall time: **6.857 s**.
**Speed Winner: `set_based_ctes`** — 0.1700 s, 1147.98x faster than naive.
**Memory Winner: `executemany_gen`** — 25.625 MB peak RSS, +3.588 MB over the calibration worker.
@@ -0,0 +1,233 @@
# /// script
# requires-python = ">=3.10"
# dependencies = []
# ///
"""
SQLite bulk-insert benchmark: 1,000,000 rows, speed + peak RAM, per-strategy
process isolation. Stdlib only. Run: uv run bench-sqlite-bulk-ARCHITECT-anthropic-claude-fable-5.py
Design (ARCHITECT / anthropic-claude-fable-5):
* Orchestrator re-execs THIS file as a subprocess per strategy
(`--worker NAME`), so peak RSS readings never pollute each other.
* Every strategy inserts IDENTICAL deterministic data into an IDENTICAL
fresh schema, verified by count + checksum.
* Timing covers the full insert phase (row generation + bind + commit),
identical work for all strategies.
* The naive autocommit baseline is fsync-bound (~50-200 rows/ms is
impossible; it does one journal sync per row). It is SAMPLED at 20,000
rows and honestly scaled x50; the table flags it with '*'.
* Peak RAM = ru_maxrss of the worker process (normalized to bytes across
macOS/Linux), plus a no-op calibration worker so a delta over
interpreter baseline is reported.
"""
import json
import os
import resource
import sqlite3
import subprocess
import sys
import tempfile
import time
N = 1_000_000
NAIVE_SAMPLE_N = 20_000 # honest sample; scaled x(N/NAIVE_SAMPLE_N) in report
SCHEMA = "CREATE TABLE t (a INTEGER, b REAL, c TEXT)"
INSERT = "INSERT INTO t VALUES (?,?,?)"
EXPECTED_SUM = lambda n: n * (n - 1) // 2 # sum of column a for n rows
# ---------------------------------------------------------------- data ----
def rows(n):
"""Identical deterministic data for every strategy."""
for i in range(n):
yield (i, i * 0.5, f"payload-{i:012d}")
# ---------------------------------------------------------- strategies ----
def s_naive_autocommit(conn, n):
conn.isolation_level = None # every INSERT commits (journal sync per row)
for r in rows(n):
conn.execute(INSERT, r)
def s_one_big_txn_loop(conn, n):
conn.execute("BEGIN")
for r in rows(n):
conn.execute(INSERT, r)
conn.commit()
def s_executemany_list(conn, n):
data = list(rows(n)) # deliberately materialized: shows the RAM cost
conn.execute("BEGIN")
conn.executemany(INSERT, data)
conn.commit()
def s_executemany_gen(conn, n):
conn.execute("BEGIN")
conn.executemany(INSERT, rows(n))
conn.commit()
def s_wal_tuned_gen(conn, n):
conn.execute("PRAGMA journal_mode=WAL")
conn.execute("PRAGMA synchronous=NORMAL")
conn.execute("PRAGMA cache_size=-64000") # 64 MB page cache
conn.execute("BEGIN")
conn.executemany(INSERT, rows(n))
conn.commit()
def s_max_tuned_gen(conn, n):
"""Predicted overall speed winner within stdlib: one txn +
executemany(generator) + journaling/sync disabled for the load."""
conn.execute("PRAGMA journal_mode=OFF")
conn.execute("PRAGMA synchronous=OFF")
conn.execute("PRAGMA cache_size=-64000")
conn.execute("PRAGMA temp_store=MEMORY")
conn.execute("BEGIN")
conn.executemany(INSERT, rows(n))
conn.commit()
STRATEGIES = {
"naive_autocommit": (s_naive_autocommit, NAIVE_SAMPLE_N),
"one_big_txn_loop": (s_one_big_txn_loop, N),
"executemany_list": (s_executemany_list, N),
"executemany_gen": (s_executemany_gen, N),
"wal_tuned_gen": (s_wal_tuned_gen, N),
"max_tuned_gen": (s_max_tuned_gen, N),
}
# Optional bonus (external `sqlite3` CLI, skipped gracefully if absent).
# Same identical data, same isolation, timing includes writing the CSV.
CLI_STRATEGY = "cli_dot_import"
def run_cli_import(dbpath, n):
import csv
import shutil
exe = shutil.which("sqlite3")
if not exe:
return False
csvpath = dbpath + ".csv"
with open(csvpath, "w", newline="") as f:
w = csv.writer(f)
for r in rows(n):
w.writerow(r)
subprocess.run(
[exe, dbpath, SCHEMA + ";", ".mode csv", f".import {csvpath} t"],
check=True, capture_output=True,
)
os.remove(csvpath)
return True
# --------------------------------------------------------------- worker ----
def rss_bytes(who=resource.RUSAGE_SELF):
v = resource.getrusage(who).ru_maxrss
return v if sys.platform == "darwin" else v * 1024 # KB on Linux
def worker(name):
if name == "calibration": # no-op: interpreter + sqlite3 import baseline
print(json.dumps({"name": name, "rows": 0, "seconds": 0.0,
"peak_rss": rss_bytes(), "ok": True}))
return
dbpath = tempfile.mktemp(suffix=".db", dir=tempfile.gettempdir())
try:
if name == CLI_STRATEGY:
t0 = time.perf_counter()
ok = run_cli_import(dbpath, N)
dt = time.perf_counter() - t0
if not ok:
print(json.dumps({"name": name, "skipped": True}))
return
n = N
else:
fn, n = STRATEGIES[name]
conn = sqlite3.connect(dbpath)
conn.execute(SCHEMA)
t0 = time.perf_counter()
fn(conn, n)
dt = time.perf_counter() - t0
conn.close()
vconn = sqlite3.connect(dbpath)
cnt, chk = vconn.execute("SELECT count(*), sum(a) FROM t").fetchone()
vconn.close()
peak = max(rss_bytes(), rss_bytes(resource.RUSAGE_CHILDREN))
print(json.dumps({
"name": name, "rows": cnt, "seconds": dt, "peak_rss": peak,
"ok": cnt == n and chk == EXPECTED_SUM(n),
}))
finally:
for p in (dbpath, dbpath + "-wal", dbpath + "-shm", dbpath + ".csv"):
if os.path.exists(p):
os.remove(p)
# ----------------------------------------------------------- orchestrator --
def spawn(name):
out = subprocess.run([sys.executable, os.path.abspath(__file__),
"--worker", name],
capture_output=True, text=True, check=True)
return json.loads(out.stdout.strip().splitlines()[-1])
def main():
print(f"SQLite bulk insert benchmark | N={N:,} rows | "
f"py {sys.version.split()[0]} sqlite {sqlite3.sqlite_version} "
f"{sys.platform}\n")
base = spawn("calibration")
results = []
order = list(STRATEGIES) + [CLI_STRATEGY]
for name in order:
r = spawn(name)
if r.get("skipped"):
print(f" [skip] {name} (sqlite3 CLI not on PATH)")
continue
assert r["ok"], f"integrity check FAILED for {name}"
scale = N / r["rows"]
r["scaled"] = r["seconds"] * scale
r["sampled"] = scale > 1.0
results.append(r)
print(f" [done] {name}: {r['seconds']:.3f}s "
f"({r['rows']:,} rows{' sampled' if r['sampled'] else ''})")
naive = next(r["scaled"] for r in results if r["name"] == "naive_autocommit")
print(f"\n{'Strategy':<22}{'Time 1M rows':>14}{'Speedup':>10}"
f"{'Peak RSS':>12}{'RSS -base':>12}")
print("-" * 70)
for r in sorted(results, key=lambda r: r["scaled"]):
mark = "*" if r["sampled"] else " "
print(f"{r['name']:<22}{r['scaled']:>12.2f}s{mark}"
f"{naive / r['scaled']:>9.1f}x"
f"{r['peak_rss'] / 1e6:>10.1f}MB"
f"{(r['peak_rss'] - base['peak_rss']) / 1e6:>10.1f}MB")
print("-" * 70)
print(f"* sampled at {NAIVE_SAMPLE_N:,} rows, honestly scaled "
f"x{N // NAIVE_SAMPLE_N} | base RSS "
f"{base['peak_rss'] / 1e6:.1f}MB (no-op interpreter)")
fastest = min(results, key=lambda r: r["scaled"])
# memory winner among full-speed strategies (exclude the naive baseline)
fast = [r for r in results if not r["sampled"]]
leanest = min(fast, key=lambda r: (r["peak_rss"], r["scaled"]))
print(f"\nSPEED WINNER : {fastest['name']} "
f"({fastest['scaled']:.2f}s, {naive / fastest['scaled']:.0f}x vs naive)")
print(f"MEMORY WINNER: {leanest['name']} "
f"({leanest['peak_rss'] / 1e6:.1f}MB peak, "
f"+{(leanest['peak_rss'] - base['peak_rss']) / 1e6:.1f}MB over baseline)")
if __name__ == "__main__":
if len(sys.argv) == 3 and sys.argv[1] == "--worker":
worker(sys.argv[2])
else:
main()
+393
View File
@@ -0,0 +1,393 @@
# /// script
# requires-python = ">=3.11"
# dependencies = []
# ///
"""Benchmark seven SQLite bulk-insert strategies on speed and peak RAM.
Run with:
uv run bench.py
The orchestrator re-executes this file once per strategy. Each worker uses a
fresh process and a fresh SQLite database, so process peak-RSS measurements
cannot leak from one strategy into another. The run writes RESULTS.md next to
this script from fresh measurements every time.
"""
from __future__ import annotations
import json
import os
from pathlib import Path
import platform
import resource
import sqlite3
import subprocess
import sys
import tempfile
import time
from typing import Any, Callable, Iterator
N = 1_000_000
NAIVE_SAMPLE_N = 20_000
SCHEMA = "CREATE TABLE t (a INTEGER, b REAL, c TEXT)"
INSERT = "INSERT INTO t VALUES (?, ?, ?)"
RESULTS_PATH = Path(__file__).resolve().with_name("RESULTS.md")
SET_BASED_SQL = """
WITH RECURSIVE seq(i) AS (
SELECT 0
UNION ALL
SELECT i + 1 FROM seq WHERE i + 1 < ?
)
INSERT INTO t
SELECT i, i * 0.5, printf('payload-%012d', i)
FROM seq
"""
Row = tuple[int, float, str]
StrategyFunction = Callable[[sqlite3.Connection, int], None]
def make_row(i: int) -> Row:
"""Return the sole canonical definition of one benchmark row."""
return (i, i * 0.5, f"payload-{i:012d}")
def rows(n: int) -> Iterator[Row]:
"""Generate identical deterministic data for every Python-bound strategy."""
for i in range(n):
yield make_row(i)
# ---------------------------------------------------------------- strategies
def s_naive_autocommit(conn: sqlite3.Connection, n: int) -> None:
"""Baseline: every INSERT is its own durable transaction."""
conn.isolation_level = None
for row in rows(n):
conn.execute(INSERT, row)
def s_one_big_txn_loop(conn: sqlite3.Connection, n: int) -> None:
conn.execute("BEGIN")
for row in rows(n):
conn.execute(INSERT, row)
conn.commit()
def s_executemany_list(conn: sqlite3.Connection, n: int) -> None:
# Deliberately materialized so the benchmark measures the classic RAM cost.
data = list(rows(n))
conn.execute("BEGIN")
conn.executemany(INSERT, data)
conn.commit()
def s_executemany_gen(conn: sqlite3.Connection, n: int) -> None:
conn.execute("BEGIN")
conn.executemany(INSERT, rows(n))
conn.commit()
def s_wal_tuned_gen(conn: sqlite3.Connection, n: int) -> None:
conn.execute("PRAGMA journal_mode=WAL")
conn.execute("PRAGMA synchronous=NORMAL")
conn.execute("BEGIN")
conn.executemany(INSERT, rows(n))
conn.commit()
# Include write-back cost instead of ending with the rows in a WAL sidecar.
busy, _wal_pages, _checkpointed = conn.execute(
"PRAGMA wal_checkpoint(TRUNCATE)"
).fetchone()
if busy:
raise RuntimeError("WAL checkpoint was unexpectedly busy")
def s_max_tuned_gen(conn: sqlite3.Connection, n: int) -> None:
"""Maximum Python-bound speed; safe only for a rebuildable staging DB."""
conn.execute("PRAGMA journal_mode=OFF")
conn.execute("PRAGMA synchronous=OFF")
conn.execute("BEGIN")
conn.executemany(INSERT, rows(n))
conn.commit()
def s_set_based_ctes(conn: sqlite3.Connection, n: int) -> None:
"""Generate identical rows inside SQLite, avoiding per-row Python binds."""
conn.execute("PRAGMA journal_mode=OFF")
conn.execute("PRAGMA synchronous=OFF")
conn.execute("BEGIN")
conn.execute(SET_BASED_SQL, (n,))
conn.commit()
STRATEGIES: dict[str, tuple[StrategyFunction, int]] = {
"naive_autocommit": (s_naive_autocommit, NAIVE_SAMPLE_N),
"one_big_txn_loop": (s_one_big_txn_loop, N),
"executemany_list": (s_executemany_list, N),
"executemany_gen": (s_executemany_gen, N),
"wal_tuned_gen": (s_wal_tuned_gen, N),
"max_tuned_gen": (s_max_tuned_gen, N),
"set_based_ctes": (s_set_based_ctes, N),
}
# ------------------------------------------------------------------- workers
def rss_bytes() -> int:
"""Return this process's measured high-water resident set size in bytes."""
ru_maxrss = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
return int(ru_maxrss if sys.platform == "darwin" else ru_maxrss * 1024)
def verify(db_path: Path, n: int) -> bool:
"""Prove row count, checksums, lengths, and representative rows match."""
conn = sqlite3.connect(db_path)
try:
count, checksum, text_length = conn.execute(
"SELECT count(*), sum(a), sum(length(c)) FROM t"
).fetchone()
valid = (
count == n
and checksum == n * (n - 1) // 2
and text_length == 20 * n
)
for i in (0, 1, n // 2, n - 1):
actual = conn.execute(
"SELECT a, b, c FROM t WHERE a = ?", (i,)
).fetchone()
valid = valid and actual is not None and tuple(actual) == make_row(i)
return bool(valid)
finally:
conn.close()
def worker(name: str) -> None:
if name == "calibration":
print(
json.dumps(
{
"name": name,
"rows": 0,
"seconds": 0.0,
"peak_rss": rss_bytes(),
"ok": True,
}
)
)
return
function, n = STRATEGIES[name]
with tempfile.TemporaryDirectory(prefix=f"sqlite-bulk-{name}-") as temp_dir:
db_path = Path(temp_dir) / "benchmark.sqlite3"
conn = sqlite3.connect(db_path)
try:
conn.execute(SCHEMA)
started = time.perf_counter()
function(conn, n)
seconds = time.perf_counter() - started
finally:
conn.close()
valid = verify(db_path, n)
result = {
"name": name,
"rows": n,
"seconds": seconds,
"peak_rss": rss_bytes(),
"ok": valid,
}
print(json.dumps(result, separators=(",", ":")))
def spawn(name: str) -> dict[str, Any]:
completed = subprocess.run(
[sys.executable, os.path.abspath(__file__), "--worker", name],
check=True,
capture_output=True,
text=True,
timeout=220,
)
output_lines = completed.stdout.strip().splitlines()
if not output_lines:
raise RuntimeError(f"worker {name} produced no result")
return json.loads(output_lines[-1])
# ---------------------------------------------------------------- reporting
def measured_rows(result: dict[str, Any]) -> str:
suffix = " (sampled)" if result["sampled"] else ""
return f"{result['rows']:,}{suffix}"
def build_results_markdown(
results: list[dict[str, Any]],
base_rss: int,
total_wall_seconds: float,
) -> tuple[str, dict[str, Any], dict[str, Any]]:
baseline = next(r for r in results if r["name"] == "naive_autocommit")
baseline_time = float(baseline["scaled_seconds"])
ordered = sorted(results, key=lambda result: result["scaled_seconds"])
fastest = ordered[0]
full_runs = [result for result in results if not result["sampled"]]
leanest = min(
full_runs,
key=lambda result: (result["peak_rss"], result["scaled_seconds"]),
)
lines = [
"# SQLite Bulk Insert Benchmark Results",
"",
(
f"Fresh measurements: **{N:,} target rows**, Python "
f"{platform.python_version()}, SQLite {sqlite3.sqlite_version}, "
f"{platform.platform()}."
),
"",
(
"The timer includes row generation, binding, insertion, and commit. "
"WAL timing also includes `wal_checkpoint(TRUNCATE)`. Each strategy "
"ran in a fresh process against a fresh database."
),
"",
"| Strategy | Measured rows | Time 1M rows (s) | Speedup vs naive | Peak RAM (MB) | RSS over base (MB) |",
"|---|---:|---:|---:|---:|---:|",
]
for result in ordered:
scaled = float(result["scaled_seconds"])
peak_mb = int(result["peak_rss"]) / 1_000_000
delta_mb = (int(result["peak_rss"]) - base_rss) / 1_000_000
time_mark = "*" if result["sampled"] else ""
lines.append(
f"| `{result['name']}` | {measured_rows(result)} | "
f"{scaled:.4f}{time_mark} | {baseline_time / scaled:.2f}x | "
f"{peak_mb:.3f} | {delta_mb:.3f} |"
)
fastest_time = float(fastest["scaled_seconds"])
leanest_peak_mb = int(leanest["peak_rss"]) / 1_000_000
leanest_delta_mb = (int(leanest["peak_rss"]) - base_rss) / 1_000_000
lines.extend(
[
"",
(
f"\\* `naive_autocommit` ran {NAIVE_SAMPLE_N:,} real rows; its "
f"measured time was scaled by {N // NAIVE_SAMPLE_N}. Its RAM "
"reading is measured, not scaled, and it is excluded from the "
"memory-winner decision."
),
"",
(
"`max_tuned_gen` and `set_based_ctes` use "
"`journal_mode=OFF`/`synchronous=OFF`; they are for rebuildable "
"staging loads only."
),
"",
f"Bare-worker peak RSS calibration: **{base_rss / 1_000_000:.3f} MB**.",
f"Total benchmark wall time: **{total_wall_seconds:.3f} s**.",
"",
(
f"**Speed Winner: `{fastest['name']}`** — {fastest_time:.4f} s, "
f"{baseline_time / fastest_time:.2f}x faster than naive."
),
(
f"**Memory Winner: `{leanest['name']}`** — "
f"{leanest_peak_mb:.3f} MB peak RSS, "
f"{leanest_delta_mb:+.3f} MB over the calibration worker."
),
"",
]
)
return "\n".join(lines), fastest, leanest
def definition_of_done_gate(
results: list[dict[str, Any]],
markdown: str,
fastest: dict[str, Any],
leanest: dict[str, Any],
) -> None:
"""Hard-fail unless fresh measurements satisfy the benchmark contract."""
expected = set(STRATEGIES)
actual = {str(result["name"]) for result in results}
if actual != expected:
raise AssertionError(f"strategy mismatch: expected {expected}, got {actual}")
if not all(result["ok"] for result in results):
failed = [result["name"] for result in results if not result["ok"]]
raise AssertionError(f"integrity verification failed: {failed}")
if not all(
float(result["seconds"]) > 0 and int(result["peak_rss"]) > 0
for result in results
):
raise AssertionError("every strategy must have measured positive time and RSS")
for result in results:
required_rows = NAIVE_SAMPLE_N if result["name"] == "naive_autocommit" else N
if result["rows"] != required_rows:
raise AssertionError(f"wrong row count for {result['name']}")
if result["name"] not in markdown:
raise AssertionError(f"RESULTS.md omitted {result['name']}")
baseline = next(r for r in results if r["name"] == "naive_autocommit")
speedup = float(baseline["scaled_seconds"]) / float(fastest["scaled_seconds"])
if speedup < 5.0:
raise AssertionError(f"fastest strategy achieved only {speedup:.2f}x")
if fastest["name"] not in markdown or leanest["name"] not in markdown:
raise AssertionError("winner declaration missing from RESULTS.md")
if "Speed Winner:" not in markdown or "Memory Winner:" not in markdown:
raise AssertionError("RESULTS.md lacks winner lines")
if leanest["name"] == "naive_autocommit":
raise AssertionError("sampled baseline cannot be Memory Winner")
def main() -> None:
total_started = time.perf_counter()
print(
f"SQLite bulk insert benchmark | N={N:,} rows | "
f"Python {platform.python_version()} | SQLite {sqlite3.sqlite_version}"
)
print("Each strategy runs in a fresh isolated worker process.\n")
calibration = spawn("calibration")
results: list[dict[str, Any]] = []
for name in STRATEGIES:
result = spawn(name)
if not result["ok"]:
raise RuntimeError(f"integrity check failed for {name}")
scale = N / int(result["rows"])
result["scaled_seconds"] = float(result["seconds"]) * scale
result["sampled"] = scale != 1.0
results.append(result)
sample_note = " sampled" if result["sampled"] else ""
print(
f"[done] {name}: {result['seconds']:.4f}s, "
f"{result['rows']:,} rows{sample_note}, "
f"peak {result['peak_rss'] / 1_000_000:.3f} MB"
)
total_wall_seconds = time.perf_counter() - total_started
markdown, fastest, leanest = build_results_markdown(
results, int(calibration["peak_rss"]), total_wall_seconds
)
definition_of_done_gate(results, markdown, fastest, leanest)
RESULTS_PATH.write_text(markdown, encoding="utf-8")
print(f"\nWrote {RESULTS_PATH}")
print(
f"Speed Winner: {fastest['name']} "
f"({fastest['scaled_seconds']:.4f}s measured/scaled to 1M)"
)
print(
f"Memory Winner: {leanest['name']} "
f"({leanest['peak_rss'] / 1_000_000:.3f} MB measured peak RSS)"
)
print("Definition-of-Done gate: PASS")
if __name__ == "__main__":
if len(sys.argv) == 3 and sys.argv[1] == "--worker":
worker(sys.argv[2])
elif len(sys.argv) == 1:
main()
else:
raise SystemExit("usage: uv run bench.py [--worker STRATEGY]")
@@ -0,0 +1,631 @@
#!/usr/bin/env -S uv run
# /// script
# requires-python = ">=3.11"
# ///
"""SQLite bulk-insert benchmark: elapsed time and process peak RSS.
Run:
uv run sqlite-bulk-benchmark-BUILDER-openai-gpt-5.6-sol.py
Every timed trial executes in a fresh child process against a fresh database.
The deliberately slow autocommit case uses a prefix sample and reports a
clearly marked linear projection to one million rows.
"""
from __future__ import annotations
import argparse
import gc
import itertools
import json
import os
import platform
import random
import sqlite3
import subprocess
import sys
import tempfile
import time
from dataclasses import dataclass
from pathlib import Path
from typing import Iterable, Iterator, Sequence
IDENTITY = "BUILDER-openai-gpt-5.6-sol"
DEFAULT_ROWS = 1_000_000
DEFAULT_BASELINE_ROWS = 10_000
DEFAULT_REPEATS = 2
EXECUTEMANY_BATCH_ROWS = 10_000
MULTI_VALUES_TARGET_ROWS = 500
COLUMN_COUNT = 6
INSERT_ONE = """
INSERT INTO events
(id, account_id, created_at, amount_cents, status, payload)
VALUES (?, ?, ?, ?, ?, ?)
"""
SCHEMA = """
CREATE TABLE events (
id INTEGER PRIMARY KEY,
account_id INTEGER NOT NULL,
created_at INTEGER NOT NULL,
amount_cents INTEGER NOT NULL,
status TEXT NOT NULL CHECK (status IN ('new', 'paid', 'sent', 'void')),
payload TEXT NOT NULL
)
"""
SET_BASED_INSERT = """
WITH RECURSIVE
seq(i) AS (
VALUES(0)
UNION ALL
SELECT i + 1 FROM seq WHERE i + 1 < ?
),
generated(i, mix) AS (
SELECT i, (i * 1103515245 + 12345) % 2147483648
FROM seq
)
INSERT INTO events
(id, account_id, created_at, amount_cents, status, payload)
SELECT
i,
mix % 100003,
1700000000 + i,
(mix % 2000001) - 1000000,
CASE i % 4
WHEN 0 THEN 'new'
WHEN 1 THEN 'paid'
WHEN 2 THEN 'sent'
ELSE 'void'
END,
printf(
'event-%010d-%010x-abcdefghijklmnopqrstuvwxyz0123456789',
i,
mix
)
FROM generated
"""
DIGEST_SQL = """
SELECT
count(*),
sum(id),
sum(account_id),
sum(created_at),
sum(amount_cents),
sum(length(status)),
sum(length(payload)),
min(id),
max(id),
min(payload),
max(payload)
FROM events
"""
@dataclass(frozen=True)
class Strategy:
key: str
label: str
journal_mode: str
synchronous: str
implementation: str
durability: str
sampled: bool = False
STRATEGIES: tuple[Strategy, ...] = (
Strategy(
"naive_autocommit",
"naive autocommit",
"DELETE",
"FULL",
"naive",
"durable",
sampled=True,
),
Strategy(
"one_big_transaction",
"one transaction + execute",
"DELETE",
"FULL",
"one_transaction",
"durable",
),
Strategy(
"executemany_streamed",
"streamed executemany batches",
"DELETE",
"FULL",
"executemany",
"durable",
),
Strategy(
"wal_multi_values",
"WAL/NORMAL + multi-VALUES",
"WAL",
"NORMAL",
"multi_values",
"consistent; latest commit power-loss risk",
),
Strategy(
"off_multi_values",
"OFF/OFF + multi-VALUES",
"OFF",
"OFF",
"multi_values",
"rebuildable staging only",
),
Strategy(
"wal_set_based",
"WAL/NORMAL + INSERT-SELECT",
"WAL",
"NORMAL",
"set_based",
"consistent; latest commit power-loss risk",
),
Strategy(
"off_set_based",
"OFF/OFF + INSERT-SELECT",
"OFF",
"OFF",
"set_based",
"rebuildable staging only",
),
)
STRATEGY_BY_KEY = {strategy.key: strategy for strategy in STRATEGIES}
Row = tuple[int, int, int, int, str, str]
Digest = tuple[int | str, ...]
def make_row(i: int) -> Row:
"""The sole Python definition of benchmark data."""
mix = (i * 1103515245 + 12345) % 2147483648
status = ("new", "paid", "sent", "void")[i % 4]
return (
i,
mix % 100003,
1700000000 + i,
(mix % 2000001) - 1000000,
status,
f"event-{i:010d}-{mix:010x}-abcdefghijklmnopqrstuvwxyz0123456789",
)
def generated_rows(start: int, stop: int) -> Iterator[Row]:
for i in range(start, stop):
yield make_row(i)
def expected_digest(row_count: int) -> Digest:
count = 0
id_sum = account_sum = created_sum = amount_sum = 0
status_length_sum = payload_length_sum = 0
min_payload: str | None = None
max_payload: str | None = None
for row in generated_rows(0, row_count):
row_id, account_id, created_at, amount, status, payload = row
count += 1
id_sum += row_id
account_sum += account_id
created_sum += created_at
amount_sum += amount
status_length_sum += len(status)
payload_length_sum += len(payload)
if min_payload is None or payload < min_payload:
min_payload = payload
if max_payload is None or payload > max_payload:
max_payload = payload
return (
count,
id_sum,
account_sum,
created_sum,
amount_sum,
status_length_sum,
payload_length_sum,
0 if row_count else None,
row_count - 1 if row_count else None,
min_payload,
max_payload,
)
def peak_rss_bytes() -> int:
if os.name == "nt":
# ctypes is standard library. PeakWorkingSetSize is the Windows
# equivalent of process peak RSS.
import ctypes
from ctypes import wintypes
class ProcessMemoryCounters(ctypes.Structure):
_fields_ = (
("cb", wintypes.DWORD),
("PageFaultCount", wintypes.DWORD),
("PeakWorkingSetSize", ctypes.c_size_t),
("WorkingSetSize", ctypes.c_size_t),
("QuotaPeakPagedPoolUsage", ctypes.c_size_t),
("QuotaPagedPoolUsage", ctypes.c_size_t),
("QuotaPeakNonPagedPoolUsage", ctypes.c_size_t),
("QuotaNonPagedPoolUsage", ctypes.c_size_t),
("PagefileUsage", ctypes.c_size_t),
("PeakPagefileUsage", ctypes.c_size_t),
)
counters = ProcessMemoryCounters()
counters.cb = ctypes.sizeof(counters)
get_current_process = ctypes.windll.kernel32.GetCurrentProcess
get_current_process.restype = wintypes.HANDLE
ok = ctypes.windll.psapi.GetProcessMemoryInfo(
get_current_process(), ctypes.byref(counters), counters.cb
)
if not ok:
raise ctypes.WinError()
return int(counters.PeakWorkingSetSize)
import resource
raw = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
# macOS reports bytes; Linux and the BSDs report KiB.
if sys.platform == "darwin":
return int(raw)
return int(raw) * 1024
def setup_connection(db_path: Path, strategy: Strategy) -> sqlite3.Connection:
connection = sqlite3.connect(db_path, isolation_level=None, timeout=60.0)
connection.execute("PRAGMA page_size=4096")
actual_journal = connection.execute(
f"PRAGMA journal_mode={strategy.journal_mode}"
).fetchone()[0]
if str(actual_journal).upper() != strategy.journal_mode:
raise RuntimeError(
f"requested journal_mode={strategy.journal_mode}, got {actual_journal}"
)
connection.execute(f"PRAGMA synchronous={strategy.synchronous}")
# Common controls: bounded cache, no mmap RSS ambiguity, disk-backed temp
# structures, and spill enabled. These are identical for every strategy.
connection.execute("PRAGMA cache_size=-2048")
connection.execute("PRAGMA cache_spill=ON")
connection.execute("PRAGMA temp_store=FILE")
connection.execute("PRAGMA mmap_size=0")
connection.execute("PRAGMA foreign_keys=ON")
connection.execute(SCHEMA)
return connection
def finish_commit(connection: sqlite3.Connection, strategy: Strategy) -> None:
connection.commit()
# Include WAL's eventual write-back cost, rather than declaring victory
# while the complete database still resides in a large sidecar file.
if strategy.journal_mode == "WAL":
result = connection.execute("PRAGMA wal_checkpoint(TRUNCATE)").fetchone()
if result[0] != 0:
raise RuntimeError(f"WAL checkpoint was busy: {result!r}")
def insert_naive(connection: sqlite3.Connection, row_count: int, strategy: Strategy) -> None:
# isolation_level=None makes every statement its own transaction.
for row in generated_rows(0, row_count):
connection.execute(INSERT_ONE, row)
def insert_one_transaction(
connection: sqlite3.Connection, row_count: int, strategy: Strategy
) -> None:
connection.execute("BEGIN IMMEDIATE")
for row in generated_rows(0, row_count):
connection.execute(INSERT_ONE, row)
finish_commit(connection, strategy)
def insert_executemany(
connection: sqlite3.Connection, row_count: int, strategy: Strategy
) -> None:
connection.execute("BEGIN IMMEDIATE")
for start in range(0, row_count, EXECUTEMANY_BATCH_ROWS):
stop = min(start + EXECUTEMANY_BATCH_ROWS, row_count)
# An iterator, not a list: batching does not materialize input rows.
connection.executemany(INSERT_ONE, generated_rows(start, stop))
finish_commit(connection, strategy)
def insert_multi_values(
connection: sqlite3.Connection, row_count: int, strategy: Strategy
) -> None:
try:
variable_limit = connection.getlimit(sqlite3.SQLITE_LIMIT_VARIABLE_NUMBER)
except AttributeError:
variable_limit = 999
batch_rows = max(
1, min(MULTI_VALUES_TARGET_ROWS, variable_limit // COLUMN_COUNT)
)
placeholder = "(" + ",".join("?" for _ in range(COLUMN_COUNT)) + ")"
connection.execute("BEGIN IMMEDIATE")
for start in range(0, row_count, batch_rows):
stop = min(start + batch_rows, row_count)
batch = list(generated_rows(start, stop))
statement = (
"INSERT INTO events "
"(id, account_id, created_at, amount_cents, status, payload) VALUES "
+ ",".join(itertools.repeat(placeholder, len(batch)))
)
bindings = [value for row in batch for value in row]
connection.execute(statement, bindings)
finish_commit(connection, strategy)
def insert_set_based(
connection: sqlite3.Connection, row_count: int, strategy: Strategy
) -> None:
connection.execute("BEGIN IMMEDIATE")
connection.execute(SET_BASED_INSERT, (row_count,))
finish_commit(connection, strategy)
IMPLEMENTATIONS = {
"naive": insert_naive,
"one_transaction": insert_one_transaction,
"executemany": insert_executemany,
"multi_values": insert_multi_values,
"set_based": insert_set_based,
}
def worker(strategy_key: str, db_path: Path, row_count: int) -> int:
strategy = STRATEGY_BY_KEY[strategy_key]
if db_path.exists():
db_path.unlink()
connection = setup_connection(db_path, strategy)
implementation = IMPLEMENTATIONS[strategy.implementation]
gc_was_enabled = gc.isenabled()
gc.disable()
started_ns = time.perf_counter_ns()
try:
implementation(connection, row_count, strategy)
except BaseException:
if connection.in_transaction:
connection.rollback()
raise
finally:
if gc_was_enabled:
gc.enable()
elapsed_ns = time.perf_counter_ns() - started_ns
# Snapshot peak before the untimed validation query.
rss_bytes = peak_rss_bytes()
digest = tuple(connection.execute(DIGEST_SQL).fetchone())
quick_check = connection.execute("PRAGMA quick_check").fetchone()[0]
connection.close()
print(
json.dumps(
{
"strategy": strategy.key,
"rows": row_count,
"elapsed_seconds": elapsed_ns / 1_000_000_000,
"peak_rss_bytes": rss_bytes,
"digest": digest,
"quick_check": quick_check,
},
separators=(",", ":"),
)
)
return 0
def format_seconds_list(results: Sequence[dict[str, object]]) -> str:
return ",".join(f"{float(result['elapsed_seconds']):.3f}" for result in results)
def format_rss_list(results: Sequence[dict[str, object]]) -> str:
mib = 1024 * 1024
return ",".join(f"{int(result['peak_rss_bytes']) / mib:.1f}" for result in results)
def median(values: Sequence[float]) -> float:
ordered = sorted(values)
middle = len(ordered) // 2
if len(ordered) % 2:
return ordered[middle]
return (ordered[middle - 1] + ordered[middle]) / 2
def print_table(
grouped: dict[str, list[dict[str, object]]],
total_rows: int,
expected_by_count: dict[int, Digest],
) -> tuple[Strategy, Strategy]:
headers = (
"Strategy",
"Mode",
"Rows/run",
"Trial time(s)",
"Time@1M(s)",
"Peak RAM/run MiB",
"Data",
)
rows: list[tuple[str, ...]] = []
normalized_times: dict[str, float] = {}
peak_by_strategy: dict[str, int] = {}
for strategy in STRATEGIES:
results = grouped[strategy.key]
elapsed = [float(result["elapsed_seconds"]) for result in results]
row_count = int(results[0]["rows"])
projected = median(elapsed) * total_rows / row_count
normalized_times[strategy.key] = projected
peak_by_strategy[strategy.key] = max(
int(result["peak_rss_bytes"]) for result in results
)
verified = all(
tuple(result["digest"]) == expected_by_count[int(result["rows"])]
and result["quick_check"] == "ok"
for result in results
)
projection_marker = "*" if strategy.sampled else ""
rows.append(
(
strategy.label,
f"{strategy.journal_mode}/{strategy.synchronous}",
f"{row_count:,}",
format_seconds_list(results),
f"{projected:.3f}{projection_marker}",
format_rss_list(results),
"OK" if verified else "FAIL",
)
)
widths = [len(header) for header in headers]
for row in rows:
for index, value in enumerate(row):
widths[index] = max(widths[index], len(value))
def render(row: Sequence[str]) -> str:
return " | ".join(value.ljust(widths[i]) for i, value in enumerate(row))
print(render(headers))
print("-+-".join("-" * width for width in widths))
for row in rows:
print(render(row))
speed_key = min(normalized_times, key=normalized_times.get)
# The sampled baseline is not eligible for the memory title: its peak RSS
# was measured honestly but it did not execute the full row count.
full_keys = [strategy.key for strategy in STRATEGIES if not strategy.sampled]
memory_key = min(full_keys, key=peak_by_strategy.get)
return STRATEGY_BY_KEY[speed_key], STRATEGY_BY_KEY[memory_key]
def orchestrator(args: argparse.Namespace) -> int:
if args.rows <= 0 or args.baseline_rows <= 0 or args.repeats <= 0:
raise SystemExit("--rows, --baseline-rows, and --repeats must be positive")
baseline_rows = min(args.baseline_rows, args.rows)
print("SQLite bulk insert benchmark")
print(f"Identity: {IDENTITY}")
print(f"Python: {platform.python_version()} ({sys.executable})")
print(f"SQLite: {sqlite3.sqlite_version}")
print(f"Platform: {platform.platform()}")
print(f"Rows: {args.rows:,}")
print(
f"Autocommit sample: {baseline_rows:,} rows; its Time@1M is a linear projection"
)
print(f"Full-strategy trials: {args.repeats}")
print(
"Timed interval: first BEGIN/INSERT through COMMIT; WAL trials also include "
"TRUNCATE checkpoint"
)
print("Peak RAM: per-worker process ru_maxrss; every trial uses a fresh process")
print()
print("Computing independent expected data digests...")
expected_by_count = {
baseline_rows: expected_digest(baseline_rows),
args.rows: expected_digest(args.rows),
}
jobs: list[tuple[Strategy, int, int]] = []
for strategy in STRATEGIES:
trial_count = 1 if strategy.sampled else args.repeats
row_count = baseline_rows if strategy.sampled else args.rows
for trial in range(1, trial_count + 1):
jobs.append((strategy, trial, row_count))
random.Random(0x5A17E).shuffle(jobs)
grouped: dict[str, list[dict[str, object]]] = {
strategy.key: [] for strategy in STRATEGIES
}
script_path = Path(__file__).resolve()
child_environment = os.environ.copy()
child_environment["PYTHONHASHSEED"] = "0"
with tempfile.TemporaryDirectory(prefix=f"sqlite-bench-{IDENTITY}-") as temp_name:
temp_dir = Path(temp_name)
for job_number, (strategy, trial, row_count) in enumerate(jobs, start=1):
db_path = temp_dir / f"{IDENTITY}-{strategy.key}-trial-{trial}.sqlite3"
print(
f"[{job_number:02d}/{len(jobs):02d}] {strategy.label}, "
f"trial {trial}, {row_count:,} rows...",
flush=True,
)
completed = subprocess.run(
[
sys.executable,
str(script_path),
"--worker",
strategy.key,
"--db",
str(db_path),
"--worker-rows",
str(row_count),
],
check=False,
capture_output=True,
text=True,
env=child_environment,
timeout=args.worker_timeout,
)
if completed.returncode != 0:
raise RuntimeError(
f"worker failed for {strategy.key}:\n"
f"stdout:\n{completed.stdout}\n"
f"stderr:\n{completed.stderr}"
)
result = json.loads(completed.stdout.strip().splitlines()[-1])
grouped[strategy.key].append(result)
for results in grouped.values():
results.sort(key=lambda item: float(item["elapsed_seconds"]))
print("\nResults")
speed_winner, memory_winner = print_table(
grouped, args.rows, expected_by_count
)
print(
"\n* naive autocommit Time@1M is measured-time × "
f"{args.rows:,}/{baseline_rows:,}; its RAM is measured, not projected."
)
print(
"OFF/OFF rows are semantically identical but the database is rebuildable-only; "
"a crash can corrupt it."
)
print(
"INSERT-SELECT is applicable when rows can be generated in SQL or selected "
"from an attached SQLite source."
)
print(f"\nSpeed Winner: {speed_winner.label}")
print(f"Memory Winner: {memory_winner.label}")
return 0
def parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--rows", type=int, default=DEFAULT_ROWS)
parser.add_argument("--baseline-rows", type=int, default=DEFAULT_BASELINE_ROWS)
parser.add_argument("--repeats", type=int, default=DEFAULT_REPEATS)
parser.add_argument("--worker-timeout", type=float, default=180.0)
parser.add_argument("--worker", choices=tuple(STRATEGY_BY_KEY))
parser.add_argument("--db", type=Path)
parser.add_argument("--worker-rows", type=int)
args = parser.parse_args(argv)
if args.worker and (args.db is None or args.worker_rows is None):
parser.error("--worker requires --db and --worker-rows")
return args
def main(argv: Sequence[str] | None = None) -> int:
args = parse_args(argv)
if args.worker:
return worker(args.worker, args.db, args.worker_rows)
return orchestrator(args)
if __name__ == "__main__":
raise SystemExit(main())