text: Inline `text::Anchor`'s timestamp field, shrinking its size (#49703)

Lukas Wirth created

This shrinks the size of `text::Anchor` from 32 bytes to 24 and
`multi_buffer::Anchor` from 72 bytes to 56

Release Notes:

- Improved the memory usage of Zed a bit

Change summary

crates/assistant_text_thread/src/text_thread.rs |  9 +-
crates/language/src/proto.rs                    | 29 +++++----
crates/project/src/lsp_store.rs                 |  2 
crates/text/src/anchor.rs                       | 55 ++++++++++++++---
crates/text/src/text.rs                         | 56 +++++++++---------
5 files changed, 95 insertions(+), 56 deletions(-)

Detailed changes

crates/assistant_text_thread/src/text_thread.rs 🔗

@@ -1094,7 +1094,7 @@ impl TextThread {
                 .buffer
                 .read(cx)
                 .version
-                .observed(anchor.start.timestamp),
+                .observed(anchor.start.timestamp()),
             TextThreadOperation::UpdateMessage { message_id, .. } => {
                 self.messages_metadata.contains_key(message_id)
             }
@@ -1121,10 +1121,11 @@ impl TextThread {
         cx: &App,
     ) -> bool {
         let version = &self.buffer.read(cx).version;
-        let observed_start =
-            range.start.is_min() || range.start.is_max() || version.observed(range.start.timestamp);
+        let observed_start = range.start.is_min()
+            || range.start.is_max()
+            || version.observed(range.start.timestamp());
         let observed_end =
-            range.end.is_min() || range.end.is_max() || version.observed(range.end.timestamp);
+            range.end.is_min() || range.end.is_max() || version.observed(range.end.timestamp());
         observed_start && observed_end
     }
 

crates/language/src/proto.rs 🔗

@@ -251,9 +251,10 @@ pub fn serialize_diagnostics<'a>(
 
 /// Serializes an [`Anchor`] to be sent over RPC.
 pub fn serialize_anchor(anchor: &Anchor) -> proto::Anchor {
+    let timestamp = anchor.timestamp();
     proto::Anchor {
-        replica_id: anchor.timestamp.replica_id.as_u16() as u32,
-        timestamp: anchor.timestamp.value,
+        replica_id: timestamp.replica_id.as_u16() as u32,
+        timestamp: timestamp.value,
         offset: anchor.offset as u64,
         bias: match anchor.bias {
             Bias::Left => proto::Bias::Left as i32,
@@ -485,18 +486,20 @@ pub fn deserialize_anchor(anchor: proto::Anchor) -> Option<Anchor> {
     } else {
         None
     };
-    Some(Anchor {
-        timestamp: clock::Lamport {
-            replica_id: ReplicaId::new(anchor.replica_id as u16),
-            value: anchor.timestamp,
-        },
-        offset: anchor.offset as usize,
-        bias: match proto::Bias::from_i32(anchor.bias)? {
-            proto::Bias::Left => Bias::Left,
-            proto::Bias::Right => Bias::Right,
-        },
+    let timestamp = clock::Lamport {
+        replica_id: ReplicaId::new(anchor.replica_id as u16),
+        value: anchor.timestamp,
+    };
+    let bias = match proto::Bias::from_i32(anchor.bias)? {
+        proto::Bias::Left => Bias::Left,
+        proto::Bias::Right => Bias::Right,
+    };
+    Some(Anchor::new(
+        timestamp,
+        anchor.offset as usize,
+        bias,
         buffer_id,
-    })
+    ))
 }
 
 /// Returns a `[clock::Lamport`] timestamp for the given [`proto::Operation`].

crates/project/src/lsp_store.rs 🔗

@@ -5598,7 +5598,7 @@ impl LspStore {
 
                 buffer
                     .update(cx, |buffer, _| {
-                        buffer.wait_for_edits(Some(position.timestamp))
+                        buffer.wait_for_edits(Some(position.timestamp()))
                     })
                     .await?;
                 this.update(cx, |this, cx| {

crates/text/src/anchor.rs 🔗

@@ -9,9 +9,15 @@ use sum_tree::{Bias, Dimensions};
 #[doc(alias = "TextAnchor")]
 #[derive(Copy, Clone, Eq, PartialEq, Hash)]
 pub struct Anchor {
-    /// The timestamp of the operation that inserted the text
-    /// in which this anchor is located.
-    pub timestamp: clock::Lamport,
+    // /// The timestamp of the operation that inserted the text
+    // /// in which this anchor is located.
+    // pub(crate) timestamp: clock::Lamport,
+    // we store the replica id and sequence number of the timestamp inline
+    // to avoid the alignment of our fields from increasing the size of this struct
+    // This saves 8 bytes, by allowing replica id, value and bias to occupy the padding
+    timestamp_replica_id: clock::ReplicaId,
+    timestamp_value: clock::Seq,
+
     /// The byte offset into the text inserted in the operation
     /// at `timestamp`.
     pub offset: usize,
@@ -31,7 +37,7 @@ impl Debug for Anchor {
         }
 
         f.debug_struct("Anchor")
-            .field("timestamp", &self.timestamp)
+            .field("timestamp", &self.timestamp())
             .field("offset", &self.offset)
             .field("bias", &self.bias)
             .field("buffer_id", &self.buffer_id)
@@ -41,22 +47,40 @@ impl Debug for Anchor {
 
 impl Anchor {
     pub const MIN: Self = Self {
-        timestamp: clock::Lamport::MIN,
+        timestamp_replica_id: clock::Lamport::MIN.replica_id,
+        timestamp_value: clock::Lamport::MIN.value,
         offset: usize::MIN,
         bias: Bias::Left,
         buffer_id: None,
     };
 
     pub const MAX: Self = Self {
-        timestamp: clock::Lamport::MAX,
+        timestamp_replica_id: clock::Lamport::MAX.replica_id,
+        timestamp_value: clock::Lamport::MAX.value,
         offset: usize::MAX,
         bias: Bias::Right,
         buffer_id: None,
     };
 
+    pub fn new(
+        timestamp: clock::Lamport,
+        offset: usize,
+        bias: Bias,
+        buffer_id: Option<BufferId>,
+    ) -> Self {
+        Self {
+            timestamp_replica_id: timestamp.replica_id,
+            timestamp_value: timestamp.value,
+            offset,
+            bias,
+            buffer_id,
+        }
+    }
+
     pub fn min_for_buffer(buffer_id: BufferId) -> Self {
         Self {
-            timestamp: clock::Lamport::MIN,
+            timestamp_replica_id: clock::Lamport::MIN.replica_id,
+            timestamp_value: clock::Lamport::MIN.value,
             offset: usize::MIN,
             bias: Bias::Left,
             buffer_id: Some(buffer_id),
@@ -65,7 +89,8 @@ impl Anchor {
 
     pub fn max_for_buffer(buffer_id: BufferId) -> Self {
         Self {
-            timestamp: clock::Lamport::MAX,
+            timestamp_replica_id: clock::Lamport::MAX.replica_id,
+            timestamp_value: clock::Lamport::MAX.value,
             offset: usize::MAX,
             bias: Bias::Right,
             buffer_id: Some(buffer_id),
@@ -85,7 +110,7 @@ impl Anchor {
     }
 
     pub fn cmp(&self, other: &Anchor, buffer: &BufferSnapshot) -> Ordering {
-        let fragment_id_comparison = if self.timestamp == other.timestamp {
+        let fragment_id_comparison = if self.timestamp() == other.timestamp() {
             Ordering::Equal
         } else {
             buffer
@@ -164,16 +189,24 @@ impl Anchor {
     }
 
     pub fn is_min(&self) -> bool {
-        self.timestamp == clock::Lamport::MIN
+        self.timestamp() == clock::Lamport::MIN
             && self.offset == usize::MIN
             && self.bias == Bias::Left
     }
 
     pub fn is_max(&self) -> bool {
-        self.timestamp == clock::Lamport::MAX
+        self.timestamp() == clock::Lamport::MAX
             && self.offset == usize::MAX
             && self.bias == Bias::Right
     }
+
+    #[inline]
+    pub fn timestamp(&self) -> clock::Lamport {
+        clock::Lamport {
+            replica_id: self.timestamp_replica_id,
+            value: self.timestamp_value,
+        }
+    }
 }
 
 pub trait OffsetRangeExt {

crates/text/src/text.rs 🔗

@@ -1657,10 +1657,10 @@ impl Buffer {
     ) -> impl 'static + Future<Output = Result<()>> + use<It> {
         let mut futures = Vec::new();
         for anchor in anchors {
-            if !self.version.observed(anchor.timestamp) && !anchor.is_max() && !anchor.is_min() {
+            if !self.version.observed(anchor.timestamp()) && !anchor.is_max() && !anchor.is_min() {
                 let (tx, rx) = oneshot::channel();
                 self.edit_id_resolvers
-                    .entry(anchor.timestamp)
+                    .entry(anchor.timestamp())
                     .or_default()
                     .push(tx);
                 futures.push(rx);
@@ -2275,7 +2275,7 @@ impl BufferSnapshot {
             };
             assert_eq!(
                 insertion.timestamp,
-                anchor.timestamp,
+                anchor.timestamp(),
                 "invalid insertion for buffer {}@{:?} and anchor {:?}",
                 self.remote_id(),
                 self.version,
@@ -2309,13 +2309,14 @@ impl BufferSnapshot {
         } else {
             debug_assert_eq!(anchor.buffer_id, Some(self.remote_id));
             debug_assert!(
-                self.version.observed(anchor.timestamp),
+                self.version.observed(anchor.timestamp()),
                 "Anchor timestamp {:?} not observed by buffer {:?}",
-                anchor.timestamp,
+                anchor.timestamp(),
                 self.version
             );
             let item = self.try_find_fragment(anchor);
-            let Some(insertion) = item.filter(|insertion| insertion.timestamp == anchor.timestamp)
+            let Some(insertion) =
+                item.filter(|insertion| insertion.timestamp == anchor.timestamp())
             else {
                 self.panic_bad_anchor(anchor);
             };
@@ -2343,7 +2344,7 @@ impl BufferSnapshot {
                 "invalid anchor - buffer id does not match: anchor {anchor:?}; buffer id: {}, version: {:?}",
                 self.remote_id, self.version
             );
-        } else if !self.version.observed(anchor.timestamp) {
+        } else if !self.version.observed(anchor.timestamp()) {
             panic!(
                 "invalid anchor - snapshot has not observed lamport: {:?}; version: {:?}",
                 anchor, self.version
@@ -2369,7 +2370,7 @@ impl BufferSnapshot {
         } else {
             let item = self.try_find_fragment(anchor);
             item.filter(|insertion| {
-                !cfg!(debug_assertions) || insertion.timestamp == anchor.timestamp
+                !cfg!(debug_assertions) || insertion.timestamp == anchor.timestamp()
             })
             .map(|insertion| &insertion.fragment_id)
         }
@@ -2377,7 +2378,7 @@ impl BufferSnapshot {
 
     fn try_find_fragment(&self, anchor: &Anchor) -> Option<&InsertionFragment> {
         let anchor_key = InsertionFragmentKey {
-            timestamp: anchor.timestamp,
+            timestamp: anchor.timestamp(),
             split_offset: anchor.offset,
         };
         match self.insertions.find_with_prev::<InsertionFragmentKey, _>(
@@ -2453,19 +2454,20 @@ impl BufferSnapshot {
                 return Anchor::max_for_buffer(self.remote_id);
             };
             let overshoot = offset - start;
-            Anchor {
-                timestamp: fragment.timestamp,
-                offset: fragment.insertion_offset + overshoot,
+            Anchor::new(
+                fragment.timestamp,
+                fragment.insertion_offset + overshoot,
                 bias,
-                buffer_id: Some(self.remote_id),
-            }
+                Some(self.remote_id),
+            )
         }
     }
 
     pub fn can_resolve(&self, anchor: &Anchor) -> bool {
         anchor.is_min()
             || anchor.is_max()
-            || (Some(self.remote_id) == anchor.buffer_id && self.version.observed(anchor.timestamp))
+            || (Some(self.remote_id) == anchor.buffer_id
+                && self.version.observed(anchor.timestamp()))
     }
 
     pub fn clip_offset(&self, offset: usize, bias: Bias) -> usize {
@@ -2760,18 +2762,18 @@ impl<D: TextDimension + Ord, F: FnMut(&FragmentSummary) -> bool> Iterator for Ed
                 break;
             }
 
-            let start_anchor = Anchor {
-                timestamp: fragment.timestamp,
-                offset: fragment.insertion_offset,
-                bias: Bias::Right,
-                buffer_id: Some(self.buffer_id),
-            };
-            let end_anchor = Anchor {
-                timestamp: fragment.timestamp,
-                offset: fragment.insertion_offset + fragment.len,
-                bias: Bias::Left,
-                buffer_id: Some(self.buffer_id),
-            };
+            let start_anchor = Anchor::new(
+                fragment.timestamp,
+                fragment.insertion_offset,
+                Bias::Right,
+                Some(self.buffer_id),
+            );
+            let end_anchor = Anchor::new(
+                fragment.timestamp,
+                fragment.insertion_offset + fragment.len,
+                Bias::Left,
+                Some(self.buffer_id),
+            );
 
             if !fragment.was_visible(self.since, self.undos) && fragment.visible {
                 let mut visible_end = cursor.end().visible;