OXYGENHUB
# 72215f29df4c oxygen / docs / backlog-overnight-4.md
9.3 KiB · Markdown · last changed Backlog 4: S6, clone and P1 logged (refs #41) 1 month ago renderedsource blame history raw
61e9ad6d Andy McCall ↶ 1
# Overnight backlog 4: beating Git on the Linux tree
2
3
Loop 3 made oxy usable on the Linux kernel. This loop aims to make it
4
faster than Git wherever the model allows it, with the kernel tree as the
5
yardstick. Correctness is never traded for speed: every item keeps its
6
tests, and the test builds that compare an incremental structure with a
7
full computation stay on.
8
9
## The yardstick
10
11
The Linux tip on Earth (`~claude/bench/linux`, 95 943 files, 1.8 GB):
12
`o` is the oxy repository and `g` the Git one. Git is 2.53.
13
14
| Operation | oxy (2026-09-30) | Git | Target |
15
|---|---:|---:|---|
16
| init and first commit | 38.5 s, 4.4 GB peak RSS | 38.8 s | under Git, under 2 GB |
17
| status, clean | 0.41 to 0.43 s | 0.26 to 0.32 s | at or under Git |
18
| status, one file edited | 1.26 to 1.39 s | 0.26 to 0.29 s | at or under Git |
19
| diff, one file | 1.19 to 1.45 s | 0.16 to 0.25 s | at or under Git |
20
| commit, one file | about 3.1 s | 0.45 s | under 0.5 s |
21
| status, diff and commit, 1 000 files | 3.9, 2.8 and 4.9 s | 0.41, 0.95 and 0.57 s | at or under Git |
22
| log -n 100 | 1.39 s | 0.01 s | under 0.05 s |
23
| blame MAINTAINERS | 1.18 s | 0.09 s | at or under Git |
24
| checkout to the first state and back | 2.2 to 2.8 s | 0.42 to 0.47 s | at or under Git |
25
| fsck | 22.1 s | 13.1 s | at or under Git |
26
| gc | 39.7 s | 102.8 s | stays ahead |
27
| store after gc | 324 MB | 295 MB | at or under Git |
28
29
Where one-file status time goes now (Earth, loaded):
30
- index load: 0.16 to 0.28 s
31
- walk: 0.45 to 0.7 s
32
- manifest check before the snapshot: 0.1 s
33
- placeholders for unchanged files: 0.09 s
34
- tracked paths: 0.04 s
35
36
## Rules for this loop
37
38
- **Measure quietly.** Earth runs OxygenFlow jobs, for every push to the
39
  Oxygen repositories and for metalgear. Measure when `pgrep firecracker`
40
  finds nothing if you can; otherwise run oxy and Git alternately, three
41
  times each, and record the load average. Push your commits after a
42
  measurement, not before, since each push starts jobs.
43
- **Build the bench binary** from the synced source:
44
  `rsync -a --delete --exclude target --exclude .oxy ~/development/oxygen/ claude@earth:bench/oxygen/`,
45
  then `CARGO_TARGET_DIR=~/bench/idxbin cargo build --release` there. Use
46
  `~/bench/prof-status.sh`, `~/bench/vs-git.sh` and `~/bench/linux-bench.sh`,
47
  and extend them rather than writing one-off commands.
48
- **Phase timings** (`OXY_TIMINGS=1`) name where time goes. Add phases where
49
  they are missing before optimising.
50
- **An index is derived.** Every new index or cache is keyed by what it
51
  describes, can be rebuilt from the changes at any time, and has a test
52
  build check that compares it with a full computation (as the state index,
53
  tree view and kept file order do).
54
- **Formats change freely before 1.0,** but a change format bump follows the
55
  order in loop 3's backlog (old binary commits it; then the runner and hub
56
  pins; then `make install`).
57
58
## Speed
59
e4b8bcf0 Andy McCall ↶ 60
- [x] B0 **Baseline.** Update `~/bench/linux-bench.sh` for today's build (the
61e9ad6d Andy McCall ↶ 61
  state index, the manifest cache, clean and dirty status, diff, commit,
62
  log, blame, checkout, a local clone, fsck, gc, store size) with three runs
63
  each and oxy and Git alternating. Record the table in `docs/benchmarks.md`.
64
- [ ] S1 **Status and diff at Git's speed** (oxygen#41). In order, measuring
65
  after each:
66
  - the manifest as a compact binary file read without a parse per line;
67
  - the walk: compare each file's stat with the manifest without building
68
    a path string per entry (sorted walk against a sorted manifest), and
69
    skip the `.oxyignore` machinery inside tracked directories where it
70
    cannot change the answer;
71
  - no placeholder `FileState` for unchanged files (audit every user of
72
    `SnapshotResult.base_snapshot.files` first);
73
  - load the state index lazily: memory-map it and decode a file's deferred
74
    history only when that file is built;
75
  - one pass for status: the manifest check and the snapshot walk share
76
    their stats.
77
- [ ] S2 **Commit at Git's speed.** A commit extends the base it already
78
  loaded: write the new state index from the old one plus the change, rather
79
  than loading history again, and do not render what the snapshot rendered.
80
  Target: one file under 0.5 s, 1 000 files under 0.6 s.
81
- [ ] S3 **The change index: log in milliseconds.** Per state, the changes in
82
  topological order with their generation numbers and where each change's
83
  metadata is, built from the parent state's index plus the new change.
84
  `oxy log -n 100` reads 100 entries. Target: under 0.05 s.
85
- [ ] S4 **The node index: blame and file history.** Per node, the changes
86
  that touch it, so blame and `oxy log FILE` cost that file's history.
87
  Target: blame MAINTAINERS at or under Git.
88
- [ ] S5 **Checkout by comparing indexes.** Compare the two states' tree
89
  indexes by rendered content hash and write only the files that differ.
90
  Target: at or under Git.
72215f29 Andy McCall ↶ 91
- [x] S6 **fsck at or under Git**: parallel object checking and a cheaper
61e9ad6d Andy McCall ↶ 92
  state pass.
93
- [ ] S7 **The first commit ahead of Git, under 2 GB**: stream reading,
94
  hashing and storing a batch of files at a time, so the whole tree's text
95
  is never in memory at once.
96
97
## Space
98
99
- [ ] P1 **Store at or under Git after gc** (324 MB against 295 MB): measure
100
  what takes the space, then try a trained zstd dictionary per repository
101
  and packing similar objects together.
102
103
## Also
104
105
- [ ] R1 **Rebase into the working copy** (oxygen#42, Andy's choice for the
106
  overnight loop): when `oxy rebase CHANGE --onto TARGET` would be
107
  conflicted, offer to leave the conflict in the working copy to resolve and
108
  commit, as `merge` does, rather than refusing.
109
- [ ] Z1 **Closing summary.** Re-run the full benchmark and summarise, for
110
  Andy, where oxy matches or beats Git on the Linux tree, and where it does
111
  not and why.
112
113
## Log
114
115
- 2026-09-30 Backlog written from the day's measurements (state index,
116
  changed-only builds and the manifest cache already in).
e4b8bcf0 Andy McCall ↶ 117
118
- 2026-09-30 **B0** baseline (table in `docs/benchmarks.md`; `linux-bench.sh` now runs three of each, alternating, plus a local clone; the loop-3 script is kept as `linux-bench.sh.loop3`). oxy is ahead on the first commit (33.3 s against 38.2 s) and gc (48 s against 100 s); clean status is close (0.43 to 0.53 s against 0.34 to 0.46 s). Behind: dirty status and diff (1.5 to 2.2 s against 0.3 to 0.8 s), commit (2.5 to 3.1 s against 0.4 to 0.5 s), log (0.9 s against 0.00 s), blame (1.0 s against 0.07 s), checkout (3 s against 0.4 to 0.5 s), fsck (24 s against 13 s), and a new gap: a local clone takes 56 s and 3.3 GB against Git's 18 s. The store after gc is 375 MB (it now includes the 48 MB state index) against 295 MB.
bd941440 Andy McCall ↶ 119
120
- 2026-09-30 **S1, first round** (commits c9dca9ed to 077e899b; table in `docs/benchmarks.md`). A lister of our own replaces the ignore crate's walker (listing 0.5 s to 0.18 s); the manifest is a flat binary file with hashes and graph keys as bytes; the state index keeps each file's history encoded until used (a writer bug I made on the way was caught only on Earth, since a failed index read falls back to a full load: test builds now read every index written back); maps are built in path order; mimalloc is the allocator. Clean status is now ahead of Git (0.18 s against 0.22 s), the first commit too (24.4 s against 36.2 s), and gc (29.9 s against 70.3 s). One-file status is 0.47 s against 0.22 s. Log (S3, first part): metadata from the state index and a store cache, signature marks without reading changes: 0.20 s, of which 9 ms is the log. For Andy: `oxy log` checks the working copy first (about 0.18 s here); Git's log does not look at it. Dropping that check would make log about 20 ms, but the working-copy line would then show the last snapshot's state.
e82591ec Andy McCall ↶ 121
122
- 2026-10-01 00:00 **Second round** (6359f779 to 81a5bdd7; table in `docs/benchmarks.md`). Commit 0.67 s (from 0.96 s): the state index is brought up to date by replaying and rewritten only 16 changes behind; keys only for changed paths; attributes from the snapshot rather than a tree walk; the manifest checksummed and written without fsync; and a fix: after a status had seen an earlier edit, a commit kept that edit's stat in the manifest, so every later status took a full snapshot. Blame 0.16 s (from 0.59 s; Git 0.07 s), log 0.18 s, checkout 0.74 s each way (from 1.4 s) with two state indexes kept per workspace, and status right after a checkout that wrote 1 000 files 0.18 s (from 1.3 s). **R1** done: `oxy rebase --into-working-copy` (closes #42).
72215f29 Andy McCall ↶ 123
124
- 2026-10-01 00:40 **S6 done, clone level, P1 partly** (3287e4ff to cc1f1723). fsck 8.2 to 8.7 s (from 13.6 s; Git 12.6 s): objects checked a chunk at a time on every core, graphs built with their lines in parallel, and the last four change encodings kept rather than one (a batch that took inline text from the Linux tip and a later change in turn read the tip again at each switch). Local clone 16.8 to 17.6 s (from 32 s; Git 17.0 s): files added once and never edited are written from their text without a graph (test builds compare with the rendering), new files are written in parallel, rewritten ones one at a time (six times slower in parallel on ext4). A faster local clone would copy the source's packs as Git's `--local` does; that skips the import's checks and brings every object of the source, so it waits for Andy. **P1**: a trained zstd dictionary per pack: pack 293.5 MB (from 301 MB; Git's 295.5 MB), gc 33.7 s (Git 70 s). The whole `.oxy` stays larger than `.git` by the state index (52 MB, a cache; compressing it would slow every load).