| 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 |
|
| 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 |
- [ ] 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). |