Minimise allocations

This commit is contained in:
Andras Schmelczer 2026-03-11 20:43:34 +00:00
parent 72dc942be6
commit 1a984427ab
2 changed files with 211 additions and 114 deletions

View file

@ -104,28 +104,55 @@ where
}
}
pub fn get_sort_key(&self, insertion_index: usize) -> (usize, usize, usize, String) {
(
self.order(),
match self {
Operation::Delete { .. } => 1,
Operation::Insert { .. } => 2,
Operation::Equal { .. } => 3,
},
insertion_index,
// Make sure that the ordering is deterministic regardless of which text
// is left or right.
match self {
Operation::Equal { length, .. } => length.to_string(),
Operation::Insert { text, .. } => {
text.iter().map(Token::original).collect::<String>()
}
fn type_priority(&self) -> u8 {
match self {
Operation::Delete { .. } => 1,
Operation::Insert { .. } => 2,
Operation::Equal { .. } => 3,
}
}
/// Compare two operations for processing order during merging. Uses
/// (order, type, `insertion_index`) with a deterministic content
/// tiebreaker that avoids allocating.
pub fn cmp_priority(
&self,
self_index: usize,
other: &Self,
other_index: usize,
) -> std::cmp::Ordering {
self.order()
.cmp(&other.order())
.then_with(|| self.type_priority().cmp(&other.type_priority()))
.then_with(|| self_index.cmp(&other_index))
.then_with(|| self.deterministic_content_cmp(other))
}
/// Deterministic tiebreaker based on operation content, so that merge
/// results are identical regardless of which side is left vs right
fn deterministic_content_cmp(&self, other: &Self) -> std::cmp::Ordering {
match (self, other) {
(Operation::Insert { text: t1, .. }, Operation::Insert { text: t2, .. }) => {
let s1 = t1.iter().flat_map(|t| t.original().chars());
let s2 = t2.iter().flat_map(|t| t.original().chars());
s1.cmp(s2)
}
(Operation::Equal { length: l1, .. }, Operation::Equal { length: l2, .. }) => {
l1.cmp(l2)
}
(
Operation::Delete {
deleted_character_count,
deleted_character_count: c1,
..
} => deleted_character_count.to_string(),
},
)
},
Operation::Delete {
deleted_character_count: c2,
..
},
) => c1.cmp(c2),
// Different types are already ordered by type_priority
_ => std::cmp::Ordering::Equal,
}
}
/// Applies the operation to the given `StringBuilder`, returning the
@ -193,10 +220,9 @@ where
}
/// Adjusts this operation based on `previous_operation` from the other side
/// to avoid duplicating or conflicting changes. Updates
/// `previous_operation` in-place.
/// to avoid duplicating or conflicting changes
#[allow(clippy::too_many_lines)]
pub fn merge_operations(self, previous_operation: &mut Option<Self>) -> Operation<T> {
pub fn merge_operations(self, previous_operation: Option<&Self>) -> Operation<T> {
let operation = self;
match (operation, previous_operation) {
@ -295,14 +321,36 @@ where
}
(
ref operation @ Operation::Equal { ref order, .. },
ref operation @ Operation::Equal {
ref order,
#[cfg(debug_assertions)]
ref text,
..
},
Some(Operation::Equal {
order: last_equal_order,
length: last_equal_length,
#[cfg(debug_assertions)]
text: last_equal_text,
..
}),
) => {
if operation.len() == *last_equal_length && *order == *last_equal_order {
// Both sides retained the same span from the original text,
// so we deduplicate by zeroing one out. This is safe because
// both EditedTexts are derived from the same original, and
// matching (order, length) means they cover the same substring
#[cfg(debug_assertions)]
debug_assert_eq!(
text, last_equal_text,
"Equal operations with same order and length should have the same text, \
but got {operation:?} vs {:?}",
Operation::<T>::Equal {
order: *last_equal_order,
length: *last_equal_length,
text: last_equal_text.clone(),
},
);
Operation::create_equal(*order, 0)
} else {
operation.clone()
@ -329,18 +377,20 @@ where
..
} => {
#[cfg(debug_assertions)]
write!(
f,
"<equal {} from {order}>",
text.as_ref()
.map(|text| format!("'{}'", text.replace('\n', "\\n")))
.unwrap_or(format!("{length} characters")),
)?;
{
write!(
f,
"<equal {} from {order}>",
text.as_ref()
.map(|text| format!("'{}'", text.replace('\n', "\\n")))
.unwrap_or(format!("{length} characters")),
)
}
#[cfg(not(debug_assertions))]
write!(f, "<equal {length} from {order}>")?;
Ok(())
{
write!(f, "<equal {length} from {order}>")
}
}
Operation::Insert { order, text, .. } => {
write!(
@ -361,22 +411,24 @@ where
..
} => {
#[cfg(debug_assertions)]
write!(
f,
"<delete {} from {order}>",
deleted_text
.as_ref()
.map(|text| format!("'{}'", text.replace('\n', "\\n")))
.unwrap_or(format!("{deleted_character_count} characters")),
)?;
{
write!(
f,
"<delete {} from {order}>",
deleted_text
.as_ref()
.map(|text| format!("'{}'", text.replace('\n', "\\n")))
.unwrap_or(format!("{deleted_character_count} characters")),
)
}
#[cfg(not(debug_assertions))]
write!(
f,
"<delete {deleted_character_count} characters from {order}>",
)?;
Ok(())
{
write!(
f,
"<delete {deleted_character_count} characters from {order}>",
)
}
}
}
}