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. |