OXYGENHUB

Read a large change once for all of a checkout's batches 519c7c061bcb

Building graphs a batch at a time, a checkout of the Linux tree read the
81 MiB first change again for every batch of 2 048 files, for the lines
of its inline inserts. The repository now keeps the last change it read
for that, shared, so every batch after the first finds it. A local
clone of the Linux tip takes 56.8 s (from 61 s); its checkout 26.4 s.

refs #35
Author
Andy McCall @andymccall <[email protected]>
When
· 2026-09-29 23:55 UTC
Signature
✔ signed by Andy McCall
Hash
519c7c061bcb7bc33441cc09f32403d048ed21bdf12f413d4c109b38e336853a
Depends on
1d360c91a289 598009a75f6c 75167672ebcd 982864068a59 a4fad5b085c7 c863c7a7a2d1 cc7a00565161 e8d38e47e9d7 ff0af1ee299d
Depended on by
c895bc52e4e0 f53cbe7a204e 98a1620a3840 acadb5ed28a1 57d8dc05c9d4 2c75672fb2fe
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.1.0 v0.10.0 v0.11.0 v0.2.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
14 atoms, 0 tree operations, 3 files

Browse the tree at this change

modified crates/oxy-core/src/repo/mod.rs +19 −4
…
178 unchanged lines
179 179
    pub config: Config,
180 180
    /// What the working copy's file system can represent (spec §7.7).
181 181
    platform: crate::portable::Platform,
182 +
    /// The encoding of the change last read for its inline inserts.
183 +
    last_change: std::cell::RefCell<Option<(ChangeHash, std::sync::Arc<Vec<u8>>)>>,
182 184
}
183 185
184 186
/// Marker file of a bare repository, inside `.oxy/`.
…
59 unchanged lines
244 246
        write_atomic(&oxy.join("workspace"), workspace.to_hex().as_bytes())?;
245 247
        let store = Store::open(oxy.join("objects"))?;
246 248
        let repo = Repo {
249 +
            last_change: Default::default(),
247 250
            git_export_skipped: Default::default(),
248 251
            root,
249 252
            oxy,
…
43 unchanged lines
293 296
        config.save(&oxy.join("config"))?;
294 297
        let store = Store::open(oxy.join("objects"))?;
295 298
        let repo = Repo {
299 +
            last_change: Default::default(),
296 300
            git_export_skipped: Default::default(),
297 301
            root,
298 302
            oxy,
…
65 unchanged lines
364 368
                let config = Config::load(Some(&default_user_config()), &shared.join("config"))?;
365 369
                let store = Store::open(shared.join("objects"))?;
366 370
                let mut repo = Repo {
371 +
                    last_change: Default::default(),
367 372
                    git_export_skipped: Default::default(),
368 373
                    root: d.to_path_buf(),
369 374
                    oxy: shared,
…
20 unchanged lines
390 395
                };
391 396
                let store = Store::open(oxy.join("objects"))?;
392 397
                let mut repo = Repo {
398 +
                    last_change: Default::default(),
393 399
                    git_export_skipped: Default::default(),
394 400
                    root: d.to_path_buf(),
395 401
                    oxy,
…
452 unchanged lines
848 854
        )?)
849 855
    }
850 856
851 -
    /// A change's encoding, for building graphs of its inline inserts.
857 +
    /// A change's encoding, for building graphs of its inline inserts. The
858 +
    /// last one read is kept: a checkout building graphs a batch at a time
859 +
    /// reads the same large change for every batch.
852 860
    fn change_bytes(
853 861
        &self,
854 862
        h: &ChangeHash,
855 -
    ) -> std::result::Result<Vec<u8>, crate::objects::ObjectError> {
856 -
        self.store.get_typed(h, ObjectType::Change).map_err(|e| {
863 +
    ) -> std::result::Result<std::sync::Arc<Vec<u8>>, crate::objects::ObjectError> {
864 +
        if let Some((k, b)) = &*self.last_change.borrow() {
865 +
            if k == h {
866 +
                return Ok(b.clone());
867 +
            }
868 +
        }
869 +
        let b = std::sync::Arc::new(self.store.get_typed(h, ObjectType::Change).map_err(|e| {
857 870
            crate::objects::ObjectError::Schema(format!("change {}: {e}", h.short(12)))
858 -
        })
871 +
        })?);
872 +
        *self.last_change.borrow_mut() = Some((*h, b.clone()));
873 +
        Ok(b)
859 874
    }
860 875
861 876
    /// Load the dependency closure of `heads` change by change.
…
853 unchanged lines
modified crates/oxy-core/src/state.rs +3 −3
…
281 unchanged lines
282 282
        &mut self,
283 283
        nodes: &[NodeId],
284 284
        load: impl FnOnce(&[ObjectRef]) -> Result<Vec<Vec<u8>>, ObjectError>,
285 -
        read_change: impl Fn(&ChangeHash) -> Result<Vec<u8>, ObjectError>,
285 +
        read_change: impl Fn(&ChangeHash) -> Result<Arc<Vec<u8>>, ObjectError>,
286 286
    ) -> Result<(), ObjectError> {
287 287
        let Some(deferred) = self.deferred.as_mut() else {
288 288
            return Ok(());
…
19 unchanged lines
308 308
        }));
309 309
        // And the encodings of changes whose inline inserts are needed,
310 310
        // each read once.
311 -
        let mut changes: HashMap<ChangeHash, Vec<u8>> = HashMap::new();
311 +
        let mut changes: HashMap<ChangeHash, Arc<Vec<u8>>> = HashMap::new();
312 312
        for t in taken.iter().flat_map(|(_, d)| d.texts.iter()) {
313 313
            if let TextRef::Inline { change, .. } = t {
314 314
                if !changes.contains_key(change) {
…
103 unchanged lines
418 418
    pub fn load_all_graphs(
419 419
        &mut self,
420 420
        load: impl FnOnce(&[ObjectRef]) -> Result<Vec<Vec<u8>>, ObjectError>,
421 -
        read_change: impl Fn(&ChangeHash) -> Result<Vec<u8>, ObjectError>,
421 +
        read_change: impl Fn(&ChangeHash) -> Result<Arc<Vec<u8>>, ObjectError>,
422 422
    ) -> Result<(), ObjectError> {
423 423
        let nodes: Vec<NodeId> = match &self.deferred {
424 424
            Some(d) => d.keys().copied().collect(),
…
1499 unchanged lines
modified docs/benchmarks.md +1 −0
…
409 unchanged lines
410 410
|---|---:|---:|
411 411
| Before | 96 s (81.6 s in a quieter run) | 5.2 to 5.4 GB |
412 412
| After | 61.0 s | 4.1 GB |
413 +
| The change read once for all batches | 56.8 s | 4.7 GB |
413 414
| Git | 9.6 s | 469 MB |
414 415
415 416
The checkout (31 s) now spends most of its time building graphs: each
…
3 unchanged lines