OXYGENHUB

A cached diff renders no file of the base (refs #41) dd6147dcf9c2

While the status cache holds, oxy diff takes the base's tree and the
graphs of the files the working copy edits, without materializing a
snapshot of every file: render_change and diff_files_of read only the
tree. Diff of one file on the Linux tree 0.17 s to 0.15 s. Benchmarks:
the first commit after tonight's memory work.
Author
Andy McCall @andymccall <[email protected]>
When
· 2026-10-02 03:25 UTC
Signature
✔ signed by Andy McCall
Hash
dd6147dcf9c22b47a9554f274d633f4cbf1444d615a2504041e89b5225cfada0
Depends on
1d360c91a289 4f264d6f20b6 77445673174b 982864068a59
Depended on by
c2e1d09047db 74b4a3c62ea4 87828a0b1f36
In bookmarks
agents-in-readme bare-hashes carried-not-removed carry-identical doctor-trust identical-sides keyless-tags main merge-remote-bookmark only-help-103 paths-for-resolutions-and-amend push-notes show-stat subrepo-pin-state switch-says
In tags
v0.10.0 v0.11.0 v0.3.0 v0.3.1 v0.4.0 v0.4.1 v0.4.2 v0.4.3 v0.4.4 v0.4.5 v0.4.6 v0.5.0 v0.5.1 v0.6.0 v0.7.0 v0.8.0 v0.9.0
Edits
5 atoms, 0 tree operations, 3 files

Browse the tree at this change

modified crates/oxy-core/src/repo/mod.rs +22 −0
…
1968 unchanged lines
1969 1969
        self.render_base_for(change, base)
1970 1970
    }
1971 1971
1972 +
    /// The base for showing `change` alone: the graphs of the files it
1973 +
    /// edits built, and the base's tree with no file rendered, which is
1974 +
    /// all [`render_change`](snapshot::render_change) and
1975 +
    /// [`diff_files_of`](snapshot::diff_files_of) read.
1976 +
    pub(crate) fn tree_base_for(
1977 +
        &self,
1978 +
        change: &Change,
1979 +
        mut base: State,
1980 +
    ) -> Result<(State, Snapshot)> {
1981 +
        let _t = crate::timing::phase("diff: the base's tree");
1982 +
        let edited: std::collections::HashSet<NodeId> =
1983 +
            change.edits.iter().map(|e| e.node()).collect();
1984 +
        let nodes: Vec<NodeId> = edited.into_iter().collect();
1985 +
        self.load_graphs_of(&mut base, &nodes)?;
1986 +
        let snapshot = Snapshot {
1987 +
            tree: base.tree().view(),
1988 +
            files: BTreeMap::new(),
1989 +
            unrendered: BTreeSet::new(),
1990 +
        };
1991 +
        Ok((base, snapshot))
1992 +
    }
1993 +
1972 1994
    /// [`Repo::change_base`] with the base already loaded (the working
1973 1995
    /// copy's comes from the state index).
1974 1996
    pub(crate) fn render_base_for(
…
131 unchanged lines
modified crates/oxy-core/src/repo/snapshot.rs +7 −6
…
1613 unchanged lines
1614 1614
        }))
1615 1615
    }
1616 1616
1617 -
    /// The working-copy change with its base state and the base's
1618 -
    /// rendering, for showing what the working copy changes. While the
1619 -
    /// status cache holds (every file as the last snapshot left it), the
1620 -
    /// stored working-copy change is the answer and no snapshot is taken;
1621 -
    /// otherwise a snapshot makes it.
1617 +
    /// The working-copy change with its base state and a snapshot of the
1618 +
    /// base, for showing what the working copy changes ([`render_change`],
1619 +
    /// [`diff_files_of`]). While the status cache holds (every file as the
1620 +
    /// last snapshot left it), the stored working-copy change is the
1621 +
    /// answer, no snapshot is taken, and the base's snapshot holds its tree
1622 +
    /// but no file; otherwise a snapshot makes all three.
1622 1623
    pub fn working_copy_with_base(&self) -> Result<(Change, State, Snapshot)> {
1623 1624
        if self.status_by_manifest()?.is_some() {
1624 1625
            let (_, wc) = self.working_copy()?;
1625 1626
            let base = self.load_state_indexed(&wc.deps)?;
1626 -
            let (base, snapshot) = self.render_base_for(&wc, base)?;
1627 +
            let (base, snapshot) = self.tree_base_for(&wc, base)?;
1627 1628
            return Ok((wc, base, snapshot));
1628 1629
        }
1629 1630
        let snap = self.snapshot_status("oxy diff")?;
…
1247 unchanged lines
modified docs/benchmarks.md +16 −0
…
954 unchanged lines
955 955
snapshot made: an 85 MB change (21 MB packed) twice. A commit's snapshot
956 956
no longer stores it. The first commit's peak memory (S7, under 2 GB) is
957 957
still open: the walk holds every file's text.
958 +
959 +
Later the same night (oxygen#41): a commit's own snapshot no longer
960 +
encodes the working-copy change, `oxy commit` reads the new change's
961 +
metadata rather than the whole change to print its id (6 s here), line
962 +
ends take four bytes, inline lines are encoded without a value per line,
963 +
and stored texts go at most 32 MiB a batch (a directory of generated
964 +
register headers made one batch of hundreds of megabytes).
965 +
966 +
| first commit of the Linux tip | before | now | Git |
967 +
|---|---:|---:|---:|
968 +
| time | 25.2 s | **12.4 s** | 30.8 s |
969 +
| peak, counting allocator | 2.72 GiB | 2.07 GiB | |
970 +
| peak resident | 3.1 GB | 2.6 GB | 41 MB |
971 +
972 +
Under 2 GB needs the texts out of memory once read (stored as the walk
973 +
reads them, the change holding references): the design in the backlog.