| 1 |
# Benchmarks |
| 2 |
|
| 3 |
The harness is `crates/oxy-core/tests/bench.rs` (spec §25). It builds a |
| 4 |
repository through the library, one commit at a time, then times the |
| 5 |
everyday operations against thresholds: |
| 6 |
|
| 7 |
OXY_BENCH_DIR=~/.cache/oxy-bench \ |
| 8 |
cargo test --release -p oxy-core --test bench -- --ignored --nocapture |
| 9 |
|
| 10 |
`OXY_BENCH_FILES` (default 10 000) and `OXY_BENCH_CHANGES` (default |
| 11 |
5 000) set the size. The data lands under `OXY_BENCH_DIR`; `/tmp` is often |
| 12 |
a RAM disk, which hides the cost of `fsync`, so measure on a real disk. |
| 13 |
|
| 14 |
## 2026-09-28: first run |
| 15 |
|
| 16 |
Machine: 8 cores, x86-64, NVMe (ext4 on LVM), release build, warm cache. |
| 17 |
Files are 21 lines each in 100 directories; one change in ten appends a |
| 18 |
line to `hot.txt`, the rest rewrite one ordinary file. |
| 19 |
|
| 20 |
|
| 21 |
|
| 22 |
| commit: import 10 000 files | 1 031 ms | 60 000 ms | ok | |
| 23 |
| commit: one file (mean of 4 999) | 3 761 ms | 250 ms | **over** | |
| 24 |
| status: clean working copy (stat crawl) | 2 977 ms | 2 000 ms | **over** | |
| 25 |
| status: 10 files modified | 3 062 ms | 2 000 ms | **over** | |
| 26 |
| commit: 10 files | 9 089 ms | 500 ms | **over** | |
| 27 |
| log: whole bookmark | 408 ms | 2 000 ms | ok | |
| 28 |
| blame: hot file (500 lines) | 2 920 ms | 2 000 ms | **over** | |
| 29 |
| blame: ordinary file | 2 927 ms | 1 000 ms | **over** | |
| 30 |
| checkpoint | 596 ms | 10 000 ms | ok | |
| 31 |
| status after checkpoint | 2 873 ms | 2 000 ms | **over** | |
| 32 |
| push: everything to an empty remote | 782 ms | 60 000 ms | ok | |
| 33 |
| push: up to date | 4 ms | 1 000 ms | ok | |
| 34 |
| clone: everything, with checkout | 3 378 ms | 60 000 ms | ok | |
| 35 |
| fsck | 7 955 ms | 60 000 ms | ok | |
| 36 |
| gc | 9 489 ms | 60 000 ms | ok | |
| 37 |
| status after gc | 2 829 ms | 2 000 ms | **over** | |
| 38 |
|
| 39 |
Peak resident memory over the whole run: 1 405 MiB. The run took 5 h 14 min, |
| 40 |
almost all of it building the history, because each commit costs as much |
| 41 |
as a status. |
| 42 |
|
| 43 |
Smaller runs, same machine: |
| 44 |
|
| 45 |
|
| 46 |
|
| 47 |
| 1 000, 200 | 90 ms | 62 ms | 62 ms | 70 MiB | |
| 48 |
| 1 000, 800 | 166 ms | 130 ms | 119 ms | 118 MiB | |
| 49 |
| 4 000, 200 | 343 ms | 235 ms | 239 ms | 254 MiB | |
| 50 |
| 10 000, 5 000 | 3 761 ms | 2 977 ms | 2 920 ms | 1 405 MiB | |
| 51 |
|
| 52 |
### What it shows |
| 53 |
|
| 54 |
Sync, log, checkpoints and repository maintenance are fine. Everything |
| 55 |
that snapshots the working copy (status, commit, blame of the working |
| 56 |
copy's base) grows with both the number of files and the length of the |
| 57 |
history. Timing the phases of one snapshot at 4 000 files and 300 changes: |
| 58 |
|
| 59 |
|
| 60 |
|
| 61 |
| load the base state (checkpoint plus the changes since) | 75–130 ms | |
| 62 |
| materialize every file of the base state | 75 ms | |
| 63 |
| walk the working copy against the manifest | 50 ms | |
| 64 |
| build the working-copy change | 8 ms | |
| 65 |
|
| 66 |
A clean status should be a stat walk and nothing else. The fix is backlog |
| 67 |
item O10: skip loading and materializing when every file's stat matches a |
| 68 |
manifest written for the same base, make the base state lazy for the |
| 69 |
callers that need it, and cache materialized snapshots per state. The |
| 70 |
thresholds above stay as they are until then, so the harness reports the |
| 71 |
gap rather than hiding it. |