Skip to content

Commit

Permalink
perf: use byte_slice_unchecked (#149)
Browse files Browse the repository at this point in the history
* perf: faster rope comparison

* perf: faster `WithIndices`

* chore: clippy
  • Loading branch information
h-a-n-a authored Dec 16, 2024
1 parent f8ec227 commit 261f6b9
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 17 deletions.
14 changes: 14 additions & 0 deletions src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1260,6 +1260,10 @@ pub trait SourceText<'a>: Default + Clone + ToString {
/// Returns a slice of the text specified by the byte range.
fn byte_slice(&self, range: Range<usize>) -> Self;

/// Returns a slice of the text specified by the byte range without bounds checking.
#[allow(unsafe_code)]
unsafe fn byte_slice_unchecked(&self, range: Range<usize>) -> Self;

/// Returns true if the text is empty.
fn is_empty(&self) -> bool;

Expand Down Expand Up @@ -1293,6 +1297,11 @@ impl<'a> SourceText<'a> for Rope<'a> {
self.byte_slice(range)
}

#[allow(unsafe_code)]
unsafe fn byte_slice_unchecked(&self, range: Range<usize>) -> Self {
self.byte_slice_unchecked(range)
}

#[inline]
fn is_empty(&self) -> bool {
self.is_empty()
Expand Down Expand Up @@ -1330,6 +1339,11 @@ impl<'a> SourceText<'a> for &'a str {
self.get(range).unwrap_or_default()
}

#[allow(unsafe_code)]
unsafe fn byte_slice_unchecked(&self, range: Range<usize>) -> Self {
self.get_unchecked(range)
}

#[inline]
fn is_empty(&self) -> bool {
(*self).is_empty()
Expand Down
94 changes: 78 additions & 16 deletions src/rope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

use std::{
borrow::Cow,
cell::RefCell,
collections::VecDeque,
hash::Hash,
ops::{Bound, RangeBounds},
Expand Down Expand Up @@ -734,6 +733,18 @@ impl PartialEq<Rope<'_>> for Rope<'_> {
return false;
}

if let (
Rope {
repr: Repr::Light(s),
},
Rope {
repr: Repr::Light(other),
},
) = (self, other)
{
return s == other;
}

let chunks = match &self.repr {
Repr::Light(s) => &[(*s, 0)][..],
Repr::Full(data) => &data[..],
Expand All @@ -743,22 +754,63 @@ impl PartialEq<Rope<'_>> for Rope<'_> {
Repr::Full(data) => &data[..],
};

let mut cur = 0;
let other_chunk_index = RefCell::new(0);
let mut other_chunk_byte_index = 0;
let other_chunk = || other_chunks[*other_chunk_index.borrow()].0.as_bytes();
for (chunk, start_pos) in chunks.iter() {
let chunk = chunk.as_bytes();
while (cur - start_pos) < chunk.len() {
if other_chunk_byte_index >= other_chunk().len() {
other_chunk_byte_index = 0;
*other_chunk_index.borrow_mut() += 1;
let total_bytes = self.len();
let mut byte_idx = 0;

let mut chunks_idx = 0;
let mut in_chunk_byte_idx = 0;

let mut other_chunks_idx = 0;
let mut in_other_chunk_byte_idx = 0;

loop {
if byte_idx == total_bytes {
break;
}

let &(chunk, _) = &chunks[chunks_idx];
let chunk_len = chunk.len();
let &(other_chunk, _) = &other_chunks[other_chunks_idx];
let other_chunk_len = other_chunk.len();

let chunk_remaining = chunk_len - in_chunk_byte_idx;
let other_chunk_remaining = other_chunk_len - in_other_chunk_byte_idx;

match chunk_remaining.cmp(&other_chunk_remaining) {
std::cmp::Ordering::Less => {
if other_chunk
[in_other_chunk_byte_idx..in_other_chunk_byte_idx + chunk_remaining]
!= chunk[in_chunk_byte_idx..]
{
return false;
}
in_other_chunk_byte_idx += chunk_remaining;
chunks_idx += 1;
in_chunk_byte_idx = 0;
byte_idx += chunk_remaining;
}
if chunk[cur - start_pos] == other_chunk()[other_chunk_byte_index] {
cur += 1;
other_chunk_byte_index += 1;
} else {
return false;
std::cmp::Ordering::Equal => {
if chunk[in_chunk_byte_idx..]
!= other_chunk[in_other_chunk_byte_idx..]
{
return false;
}
chunks_idx += 1;
other_chunks_idx += 1;
in_chunk_byte_idx = 0;
in_other_chunk_byte_idx = 0;
byte_idx += chunk_remaining;
}
std::cmp::Ordering::Greater => {
if chunk[in_chunk_byte_idx..in_chunk_byte_idx + other_chunk_remaining]
!= other_chunk[in_other_chunk_byte_idx..]
{
return false;
}
in_chunk_byte_idx += other_chunk_remaining;
other_chunks_idx += 1;
in_other_chunk_byte_idx = 0;
byte_idx += other_chunk_remaining;
}
}
}
Expand Down Expand Up @@ -1061,6 +1113,16 @@ mod tests {
b.add("fghi");

assert_eq!(a, b);

let mut a = Rope::new();
a.add("abc");

let mut b = Rope::new();
b.add("a");
b.add("b");
b.add("c");

assert_eq!(a, b);
}

#[test]
Expand Down
9 changes: 8 additions & 1 deletion src/with_indices.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,14 @@ where
let str_len = self.line.len();
let start = *indices_indexes.get(start_index).unwrap_or(&str_len);
let end = *indices_indexes.get(end_index).unwrap_or(&str_len);
self.line.byte_slice(start..end)

#[allow(unsafe_code)]
unsafe {
// SAFETY: Since `indices` iterates over the `CharIndices` of `self`, we can guarantee
// that the indices obtained from it will always be within the bounds of `self` and they
// will always lie on UTF-8 sequence boundaries.
self.line.byte_slice_unchecked(start..end)
}
}
}

Expand Down

0 comments on commit 261f6b9

Please sign in to comment.