oficina / memoria Public

Clone
git clone https://amadan.net/oficina/memoria

Plain git — no account needed to clone.

Download

Download this file

1# Recorded measurements
2
3A review of draft 2 observed that every performance figure in §7 was an
4assertion with no benchmark, command, machine or sample behind it, and
5that the FTS size could not be reconciled with the stated schema. Both
6were fair. This file is the answer: `tools/bench` produces it, and §7
7cites it rather than restating numbers.
8
9## Machine and command
10
11```
12linux/amd64, 16 cpu, go1.26.5
13go build -o bench ./tools/bench
14./bench --corpus /home/paulca/amadan.net/oficina --chunks 90000
15```
16
17Run 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
19the 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
24650 chunks from the real corpus, **mean 202 words / 1325 bytes**.
25
26This corrects draft 2, which assumed 400 words and ~1.6 KB. The chunker
27targets 400 words but folds runts and never straddles a heading, and real
28documents have headings every few paragraphs — so the mean lands at about
29half the target. Every figure below follows from the measured size, not
30the intended one.
31
32At 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
42table keeps its own copy of the inserted text, which the earlier
43measurement did not account for; the review caught this before the
44arithmetic downstream of it was trusted.
45
46The contentless table is **4× smaller** and just as fast to query, but it
47cannot return column values, so `snippet()` does not work on it and
48memoria would have to build its own highlight from the sealed chunk. That
49is 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
53Best of three runs. Draft 2 asserted 3.2 GFLOP/s; measured, it is 3.2
54GFLOP/s, and every latency in draft 2's table was right to within a
55millisecond.
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
66At 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
73So the ceiling under a 250 MB / 5-second budget is **about 90,000–110,000
74chunks**, not the 150,000 draft 2 derived. Draft 1's ~100,000 was closer,
75and draft 2's revision moved the number the wrong way while claiming to
76have 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