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 |