OXYGENHUB

A new file's lines share its content 40a40bb7b798

A file read from the working copy is held behind a shared buffer, and
the insert that adds it splits that buffer into lines without copying
it (and without the copy the stored text made on the way). A first
commit of a large tree held its text twice.

Rendering times each of its paths (kept order, chain, single order,
general), and an import times rendering its whole tree apart, under
OXY_TIMINGS.

refs #35
Author
Andy McCall @andymccall <[email protected]>
When
· 2026-09-30 07:39 UTC
Signature
✔ signed by Andy McCall
Hash
40a40bb7b798595b5bd6835f5f509f381a2b848f940e0e5869d74879d37d2c5a
Depends on
01a47cd7f0c8 2306a4d24779 598009a75f6c 6e723b81f5b9 75167672ebcd 79ab8528ff62 982864068a59 b7f102fcbb17 c3069537d7c5 cc7a00565161 cdb5beba7f05 dbf06d342e1d dfb23a3a8cfa e7ddd552b619 ff0af1ee299d
Depended on by
c3d2ef6104c2 5b4c51307b14 36e51aa806c6 1966f5db15ee
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
33 atoms, 0 tree operations, 5 files

Browse the tree at this change

modified crates/oxy-core/src/graph.rs +13 −1
…
541 unchanged lines
542 542
            },
543 543
        };
544 544
        use std::sync::atomic::Ordering::Relaxed;
545 +
        // Which way it went, and how long it took (`OXY_TIMINGS`).
546 +
        let started = crate::timing::enabled().then(std::time::Instant::now);
547 +
        let took = |name: &'static str| {
548 +
            if let Some(t) = started {
549 +
                crate::timing::add(name, t.elapsed());
550 +
            }
551 +
        };
545 552
        if let Some(o) = self.order.get() {
546 553
            let holds = o.confirmed.load(Relaxed)
547 554
                || o.unchecked.iter().all(|k| {
…
3 unchanged lines
551 558
                });
552 559
            if holds {
553 560
                o.confirmed.store(true, Relaxed);
561 +
                took("graph: kept order");
554 562
                return lines(o.atoms.clone());
555 563
            }
556 564
            self.order.set(None);
…
1 unchanged lines
558 566
        // A chain (nothing ever deleted) is quick to walk, and a fresh tree
559 567
        // is all chains: no order is kept for one.
560 568
        if let Some(order) = self.as_chain() {
569 +
            took("graph: chain");
561 570
            return lines(order);
562 571
        }
563 572
        if let Some(order) = self.as_order(deps) {
564 573
            self.order
565 574
                .set(Some(std::sync::Arc::new(Order::known(order.clone()))));
575 +
            took("graph: single order");
566 576
            return lines(order);
567 577
        }
568 -
        self.materialize_general(deps)
578 +
        let m = self.materialize_general(deps);
579 +
        took("graph: general");
580 +
        m
569 581
    }
570 582
571 583
    /// Take the kept order, to [`Order::splice`] it once a change's edits
…
1228 unchanged lines
modified crates/oxy-core/src/lines.rs +24 −13
…
7 unchanged lines
8 8
9 9
#[derive(Default, PartialEq, Eq, Hash)]
10 10
struct Inner {
11 -
    buf: Vec<u8>,
11 +
    /// Shared with the file it was read from, when there was one.
12 +
    buf: Arc<Vec<u8>>,
12 13
    /// The end offset of each line in `buf`.
13 14
    ends: Vec<u64>,
14 15
}
…
7 unchanged lines
22 23
    /// unterminated line is its own line (the rule of
23 24
    /// [`crate::diff::split_lines`]).
24 25
    pub fn split(content: &[u8]) -> Lines {
26 +
        Lines::split_shared(Arc::new(content.to_vec()))
27 +
    }
28 +
29 +
    /// [`Lines::split`] of a buffer held elsewhere too (a file's content),
30 +
    /// shared rather than copied.
31 +
    pub fn split_shared(buf: Arc<Vec<u8>>) -> Lines {
32 +
        let content = &buf[..];
25 33
        let mut ends = Vec::new();
26 34
        for (i, &c) in content.iter().enumerate() {
27 35
            if c == b'\n' {
…
3 unchanged lines
31 39
        if ends.last().copied().unwrap_or(0) as usize != content.len() {
32 40
            ends.push(content.len() as u64);
33 41
        }
34 -
        Lines(Arc::new(Inner {
35 -
            buf: content.to_vec(),
36 -
            ends,
37 -
        }))
42 +
        Lines(Arc::new(Inner { buf, ends }))
38 43
    }
39 44
40 45
    pub fn len(&self) -> usize {
…
44 unchanged lines
85 90
86 91
impl<'a> FromIterator<&'a [u8]> for Lines {
87 92
    fn from_iter<I: IntoIterator<Item = &'a [u8]>>(iter: I) -> Lines {
88 -
        let mut inner = Inner::default();
93 +
        let (mut buf, mut ends) = (Vec::new(), Vec::new());
89 94
        for l in iter {
90 -
            inner.buf.extend_from_slice(l);
91 -
            inner.ends.push(inner.buf.len() as u64);
95 +
            buf.extend_from_slice(l);
96 +
            ends.push(buf.len() as u64);
92 97
        }
93 -
        Lines(Arc::new(inner))
98 +
        Lines(Arc::new(Inner {
99 +
            buf: Arc::new(buf),
100 +
            ends,
101 +
        }))
94 102
    }
95 103
}
96 104
97 105
impl FromIterator<Vec<u8>> for Lines {
98 106
    fn from_iter<I: IntoIterator<Item = Vec<u8>>>(iter: I) -> Lines {
99 -
        let mut inner = Inner::default();
107 +
        let (mut buf, mut ends) = (Vec::new(), Vec::new());
100 108
        for l in iter {
101 -
            inner.buf.extend_from_slice(&l);
102 -
            inner.ends.push(inner.buf.len() as u64);
109 +
            buf.extend_from_slice(&l);
110 +
            ends.push(buf.len() as u64);
103 111
        }
104 -
        Lines(Arc::new(inner))
112 +
        Lines(Arc::new(Inner {
113 +
            buf: Arc::new(buf),
114 +
            ends,
115 +
        }))
105 116
    }
106 117
}
107 118
…
56 unchanged lines
modified crates/oxy-core/src/repo/git.rs +2 −2
…
906 unchanged lines
907 907
                crate::timing::add("render: same and need", clock.elapsed());
908 908
                let t = std::time::Instant::now();
909 909
                self.load_graphs_of(&mut base, &need)?;
910 -
                crate::timing::add("render: load graphs", t.elapsed());
910 +
                crate::timing::add("render: load graphs, whole tree", t.elapsed());
911 911
                let t = std::time::Instant::now();
912 912
                let snap = base.materialize_except(&skip);
913 -
                crate::timing::add("render: materialize", t.elapsed());
913 +
                crate::timing::add("render: materialize, whole tree", t.elapsed());
914 914
                snap
915 915
            };
916 916
…
672 unchanged lines
modified crates/oxy-core/src/repo/mod.rs +5 −4
…
134 unchanged lines
135 135
    pub kind: NodeKind,
136 136
    pub exec: bool,
137 137
    /// File content, or a symlink's target. Empty when `blob` is set.
138 -
    pub content: Vec<u8>,
138 +
    /// Shared, so a new file's insert holds it without a copy.
139 +
    pub content: std::sync::Arc<Vec<u8>>,
139 140
    /// The stored blob of an opaque file whose size and mtime are unchanged
140 141
    /// since the last manifest (spec §12.3 stat cache); the content was not
141 142
    /// read.
…
14 unchanged lines
156 157
        FsEntry {
157 158
            kind,
158 159
            exec,
159 -
            content,
160 +
            content: std::sync::Arc::new(content),
160 161
            blob: None,
161 162
            content_hash: None,
162 163
            unchanged: false,
…
1128 unchanged lines
1291 1292
            for (rel, mut e) in read {
1292 1293
                // A cached blob the store lost (gc, a partial clone): read the file after all.
1293 1294
                if e.content.is_empty() && e.blob.is_some_and(|b| !self.store.has(&b)) {
1294 -
                    e.content = fs::read(self.root.join(&rel))?;
1295 +
                    e.content = std::sync::Arc::new(fs::read(self.root.join(&rel))?);
1295 1296
                    e.blob = None;
1296 1297
                }
1297 1298
                out.insert(rel, e);
…
89 unchanged lines
1387 1388
    ) -> Result<FsEntry> {
1388 1389
        let mut e = read_file_entry(rel, path, meta, cache, None)?;
1389 1390
        if e.content.is_empty() && e.blob.is_some_and(|b| !self.store.has(&b)) {
1390 -
            e.content = fs::read(path)?;
1391 +
            e.content = std::sync::Arc::new(fs::read(path)?);
1391 1392
            e.blob = None;
1392 1393
        }
1393 1394
        Ok(e)
…
344 unchanged lines
modified crates/oxy-core/src/repo/snapshot.rs +12 −8
…
388 unchanged lines
389 389
                    Some(FileContent::Text {
390 390
                        bytes,
391 391
                        materialized,
392 -
                    }) if !materialized.is_conflicted() && *bytes == e.content => {
392 +
                    }) if !materialized.is_conflicted() && *bytes == *e.content => {
393 393
                        keys.get(p).cloned()
394 394
                    }
395 395
                    _ => None,
…
1255 unchanged lines
1651 1651
    }
1652 1652
1653 1653
    /// Text content as stored: `\r\n` normalized for `eol=` files.
1654 -
    fn stored_text(&self, path: &str, content: &[u8]) -> Vec<u8> {
1654 +
    fn stored_text(
1655 +
        &self,
1656 +
        path: &str,
1657 +
        content: &std::sync::Arc<Vec<u8>>,
1658 +
    ) -> std::sync::Arc<Vec<u8>> {
1655 1659
        if self.attrs.lookup(path).eol.is_some() {
1656 -
            crate::attributes::normalize_eol(content)
1660 +
            std::sync::Arc::new(crate::attributes::normalize_eol(content))
1657 1661
        } else {
1658 -
            content.to_vec()
1662 +
            content.clone()
1659 1663
        }
1660 1664
    }
1661 1665
…
111 unchanged lines
1773 1777
                node,
1774 1778
                after: crate::objects::AtomRef::Start,
1775 1779
                before: crate::objects::AtomRef::End,
1776 -
                lines: crate::lines::Lines::split(&content),
1780 +
                lines: crate::lines::Lines::split_shared(content),
1777 1781
            });
1778 1782
        }
1779 1783
        self.statuses.push(PathStatus {
…
180 unchanged lines
1960 1964
                materialized,
1961 1965
            } => {
1962 1966
                if materialized.is_conflicted() {
1963 -
                    if bytes == &disk_text {
1967 +
                    if bytes == &*disk_text {
1964 1968
                        return Ok(());
1965 1969
                    }
1966 1970
                    if has_conflict_markers(&e.content) {
…
65 unchanged lines
2032 2036
                        new: Some(blob),
2033 2037
                    });
2034 2038
                    status = Some(ChangeStatus::Modified);
2035 -
                } else if bytes != &disk_text {
2039 +
                } else if bytes != &*disk_text {
2036 2040
                    let mut laps = crate::timing::Laps::start();
2037 2041
                    // The base's rendering holds its live atoms in order,
2038 2042
                    // one per line (it is not conflicted here).
…
419 unchanged lines
2458 2462
            let score = match (&old, old_blob) {
2459 2463
                (Some(o), _) => {
2460 2464
                    if exact_only {
2461 -
                        if *o == e.content {
2465 +
                        if *o == *e.content {
2462 2466
                            100
2463 2467
                        } else {
2464 2468
                            0
…
56 unchanged lines