| 1 | # Recorded measurements |
| 2 | |
| 3 | A review of draft 2 observed that every performance figure in §7 was an |
| 4 | assertion with no benchmark, command, machine or sample behind it, and |
| 5 | that the FTS size could not be reconciled with the stated schema. Both |
| 6 | were fair. This file is the answer: `tools/bench` produces it, and §7 |
| 7 | cites it rather than restating numbers. |
| 8 | |
| 9 | ## Machine and command |
| 10 | |
| 11 | ``` |
| 12 | linux/amd64, 16 cpu, go1.26.5 |
| 13 | go build -o bench ./tools/bench |
| 14 | ./bench --corpus /home/paulca/amadan.net/oficina --chunks 90000 |
| 15 | ``` |
| 16 | |
| 17 | Run 2026-09-03. The corpus is every `.md` in the oficina tree, chunked by |
| 18 | **memoria's own chunker** (`internal/memoria/chunk`), then cycled to reach |
| 19 | the target count. Cycling repeats vocabulary, so the FTS figures are |
| 20 | **optimistic**: genuinely varied English produces a larger postings list. |
| 21 | |
| 22 | ## What a memoria chunk actually is |
| 23 | |
| 24 | 650 chunks from the real corpus, **mean 202 words / 1325 bytes**. |
| 25 | |
| 26 | This corrects draft 2, which assumed 400 words and ~1.6 KB. The chunker |
| 27 | targets 400 words but folds runts and never straddles a heading, and real |
| 28 | documents have headings every few paragraphs — so the mean lands at about |
| 29 | half the target. Every figure below follows from the measured size, not |
| 30 | the intended one. |
| 31 | |
| 32 | At 90,000 chunks that is **113.8 MB of text**. |
| 33 | |
| 34 | ## FTS5 |
| 35 | |
| 36 | | schema | index size | bytes/chunk | build | one BM25 query | |
| 37 | |---|---|---|---|---| |
| 38 | | `fts5(body, tokenize='porter unicode61')` | **200.0 MB** | 2329 | 6.8 s | 1.09 ms | |
| 39 | | `fts5(body, content='', tokenize=…)` | **49.3 MB** | 574 | 6.2 s | 1.09 ms | |
| 40 | |
| 41 | **Draft 2 claimed 104 MB. That was wrong by about half.** A default FTS5 |
| 42 | table keeps its own copy of the inserted text, which the earlier |
| 43 | measurement did not account for; the review caught this before the |
| 44 | arithmetic downstream of it was trusted. |
| 45 | |
| 46 | The contentless table is **4× smaller** and just as fast to query, but it |
| 47 | cannot return column values, so `snippet()` does not work on it and |
| 48 | memoria would have to build its own highlight from the sealed chunk. That |
| 49 | is a real trade and §7 records it as a decision rather than an accident. |
| 50 | |
| 51 | ## Brute-force cosine, one goroutine, pure Go, no SIMD |
| 52 | |
| 53 | Best of three runs. Draft 2 asserted 3.2 GFLOP/s; measured, it is 3.2 |
| 54 | GFLOP/s, and every latency in draft 2's table was right to within a |
| 55 | millisecond. |
| 56 | |
| 57 | | chunks × dims | measured | GFLOP/s | vectors f32 | vectors int8 | |
| 58 | |---|---|---|---|---| |
| 59 | | 10,000 × 512 | 3.18 ms | 3.2 | 20 MB | 5 MB | |
| 60 | | 90,000 × 512 | 28.9 ms | 3.2 | 176 MB | 44 MB | |
| 61 | | 100,000 × 768 | 49.2 ms | 3.1 | 293 MB | 73 MB | |
| 62 | | 250,000 × 512 | 80.0 ms | 3.2 | 488 MB | 122 MB | |
| 63 | |
| 64 | ## The wake footprint, recomputed |
| 65 | |
| 66 | At 90,000 chunks: |
| 67 | |
| 68 | | design | FTS | vectors | separate text | total | |
| 69 | |---|---|---|---|---| |
| 70 | | default FTS (text lives in the index) | 200 MB | 44 MB | — | **244 MB** | |
| 71 | | contentless FTS + text for snippets | 49 MB | 44 MB | 114 MB | **207 MB** | |
| 72 | |
| 73 | So the ceiling under a 250 MB / 5-second budget is **about 90,000–110,000 |
| 74 | chunks**, not the 150,000 draft 2 derived. Draft 1's ~100,000 was closer, |
| 75 | and draft 2's revision moved the number the wrong way while claiming to |
| 76 | have fixed the arithmetic. |
| 77 | |
| 78 | ## Still not measured |
| 79 | |
| 80 | - Bucket read bandwidth and AES-GCM throughput on the platform. The |
| 81 | 50–100 MB/s and ~1 GB/s in §7.3 remain assumptions, and they are the |
| 82 | inputs the ceiling is most sensitive to. |
| 83 | - The rest of a cold start: opening SQLite, allocating the vector matrix, |
| 84 | parsing the snapshot, building id maps. The 5-second budget is spent on |
| 85 | transfer and decryption alone, which flatters it. |
| 86 | - A corpus of genuinely varied English rather than a cycled one. |
| 87 | |