Skip to content

Commit 261f6b9

Browse files
authored
perf: use byte_slice_unchecked (#149)
* perf: faster rope comparison * perf: faster `WithIndices` * chore: clippy
1 parent f8ec227 commit 261f6b9

File tree

3 files changed

+100
-17
lines changed

3 files changed

+100
-17
lines changed

src/helpers.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1260,6 +1260,10 @@ pub trait SourceText<'a>: Default + Clone + ToString {
12601260
/// Returns a slice of the text specified by the byte range.
12611261
fn byte_slice(&self, range: Range<usize>) -> Self;
12621262

1263+
/// Returns a slice of the text specified by the byte range without bounds checking.
1264+
#[allow(unsafe_code)]
1265+
unsafe fn byte_slice_unchecked(&self, range: Range<usize>) -> Self;
1266+
12631267
/// Returns true if the text is empty.
12641268
fn is_empty(&self) -> bool;
12651269

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

1300+
#[allow(unsafe_code)]
1301+
unsafe fn byte_slice_unchecked(&self, range: Range<usize>) -> Self {
1302+
self.byte_slice_unchecked(range)
1303+
}
1304+
12961305
#[inline]
12971306
fn is_empty(&self) -> bool {
12981307
self.is_empty()
@@ -1330,6 +1339,11 @@ impl<'a> SourceText<'a> for &'a str {
13301339
self.get(range).unwrap_or_default()
13311340
}
13321341

1342+
#[allow(unsafe_code)]
1343+
unsafe fn byte_slice_unchecked(&self, range: Range<usize>) -> Self {
1344+
self.get_unchecked(range)
1345+
}
1346+
13331347
#[inline]
13341348
fn is_empty(&self) -> bool {
13351349
(*self).is_empty()

src/rope.rs

Lines changed: 78 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
use std::{
44
borrow::Cow,
5-
cell::RefCell,
65
collections::VecDeque,
76
hash::Hash,
87
ops::{Bound, RangeBounds},
@@ -734,6 +733,18 @@ impl PartialEq<Rope<'_>> for Rope<'_> {
734733
return false;
735734
}
736735

736+
if let (
737+
Rope {
738+
repr: Repr::Light(s),
739+
},
740+
Rope {
741+
repr: Repr::Light(other),
742+
},
743+
) = (self, other)
744+
{
745+
return s == other;
746+
}
747+
737748
let chunks = match &self.repr {
738749
Repr::Light(s) => &[(*s, 0)][..],
739750
Repr::Full(data) => &data[..],
@@ -743,22 +754,63 @@ impl PartialEq<Rope<'_>> for Rope<'_> {
743754
Repr::Full(data) => &data[..],
744755
};
745756

746-
let mut cur = 0;
747-
let other_chunk_index = RefCell::new(0);
748-
let mut other_chunk_byte_index = 0;
749-
let other_chunk = || other_chunks[*other_chunk_index.borrow()].0.as_bytes();
750-
for (chunk, start_pos) in chunks.iter() {
751-
let chunk = chunk.as_bytes();
752-
while (cur - start_pos) < chunk.len() {
753-
if other_chunk_byte_index >= other_chunk().len() {
754-
other_chunk_byte_index = 0;
755-
*other_chunk_index.borrow_mut() += 1;
757+
let total_bytes = self.len();
758+
let mut byte_idx = 0;
759+
760+
let mut chunks_idx = 0;
761+
let mut in_chunk_byte_idx = 0;
762+
763+
let mut other_chunks_idx = 0;
764+
let mut in_other_chunk_byte_idx = 0;
765+
766+
loop {
767+
if byte_idx == total_bytes {
768+
break;
769+
}
770+
771+
let &(chunk, _) = &chunks[chunks_idx];
772+
let chunk_len = chunk.len();
773+
let &(other_chunk, _) = &other_chunks[other_chunks_idx];
774+
let other_chunk_len = other_chunk.len();
775+
776+
let chunk_remaining = chunk_len - in_chunk_byte_idx;
777+
let other_chunk_remaining = other_chunk_len - in_other_chunk_byte_idx;
778+
779+
match chunk_remaining.cmp(&other_chunk_remaining) {
780+
std::cmp::Ordering::Less => {
781+
if other_chunk
782+
[in_other_chunk_byte_idx..in_other_chunk_byte_idx + chunk_remaining]
783+
!= chunk[in_chunk_byte_idx..]
784+
{
785+
return false;
786+
}
787+
in_other_chunk_byte_idx += chunk_remaining;
788+
chunks_idx += 1;
789+
in_chunk_byte_idx = 0;
790+
byte_idx += chunk_remaining;
756791
}
757-
if chunk[cur - start_pos] == other_chunk()[other_chunk_byte_index] {
758-
cur += 1;
759-
other_chunk_byte_index += 1;
760-
} else {
761-
return false;
792+
std::cmp::Ordering::Equal => {
793+
if chunk[in_chunk_byte_idx..]
794+
!= other_chunk[in_other_chunk_byte_idx..]
795+
{
796+
return false;
797+
}
798+
chunks_idx += 1;
799+
other_chunks_idx += 1;
800+
in_chunk_byte_idx = 0;
801+
in_other_chunk_byte_idx = 0;
802+
byte_idx += chunk_remaining;
803+
}
804+
std::cmp::Ordering::Greater => {
805+
if chunk[in_chunk_byte_idx..in_chunk_byte_idx + other_chunk_remaining]
806+
!= other_chunk[in_other_chunk_byte_idx..]
807+
{
808+
return false;
809+
}
810+
in_chunk_byte_idx += other_chunk_remaining;
811+
other_chunks_idx += 1;
812+
in_other_chunk_byte_idx = 0;
813+
byte_idx += other_chunk_remaining;
762814
}
763815
}
764816
}
@@ -1061,6 +1113,16 @@ mod tests {
10611113
b.add("fghi");
10621114

10631115
assert_eq!(a, b);
1116+
1117+
let mut a = Rope::new();
1118+
a.add("abc");
1119+
1120+
let mut b = Rope::new();
1121+
b.add("a");
1122+
b.add("b");
1123+
b.add("c");
1124+
1125+
assert_eq!(a, b);
10641126
}
10651127

10661128
#[test]

src/with_indices.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,14 @@ where
3939
let str_len = self.line.len();
4040
let start = *indices_indexes.get(start_index).unwrap_or(&str_len);
4141
let end = *indices_indexes.get(end_index).unwrap_or(&str_len);
42-
self.line.byte_slice(start..end)
42+
43+
#[allow(unsafe_code)]
44+
unsafe {
45+
// SAFETY: Since `indices` iterates over the `CharIndices` of `self`, we can guarantee
46+
// that the indices obtained from it will always be within the bounds of `self` and they
47+
// will always lie on UTF-8 sequence boundaries.
48+
self.line.byte_slice_unchecked(start..end)
49+
}
4350
}
4451
}
4552

0 commit comments

Comments
 (0)