OXYGENHUB
# 67a4fe1aa2d5 oxygen / docs / backlog-overnight-4.md
8.3 KiB · Markdown · last changed Benchmarks: the second round on the Linux tree (refs #41) 1 month ago renderedsource blame history raw
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
60
- [x] B0 **Baseline.** Update `~/bench/linux-bench.sh` for today's build (the
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.
91
- [ ] S6 **fsck at or under Git**: parallel object checking and a cheaper
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
- [x] 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).
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.
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.
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).