Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 87 additions & 9 deletions library/core/src/str/pattern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1071,6 +1071,7 @@ pub struct StrSearcher<'a, 'b> {
#[derive(Clone, Debug)]
enum StrSearcherImpl {
Empty(EmptyNeedle),
Byte(ByteNeedle),
TwoWay(TwoWaySearcher),
}

Expand All @@ -1084,6 +1085,16 @@ struct EmptyNeedle {
is_finished: bool,
}

/// Fast searcher for a single-byte needle using `memchr`/`memrchr`.
#[derive(Clone, Debug)]
struct ByteNeedle {
b: u8,
/// Forward cursor: `haystack[..position]` has already been reported.
position: usize,
/// Backward cursor: `haystack[end..]` has already been reported.
end: usize,
}

impl<'a, 'b> StrSearcher<'a, 'b> {
fn new(haystack: &'a str, needle: &'b str) -> StrSearcher<'a, 'b> {
if needle.is_empty() {
Expand All @@ -1098,6 +1109,12 @@ impl<'a, 'b> StrSearcher<'a, 'b> {
is_finished: false,
}),
}
} else if let &[b] = needle.as_bytes() {
StrSearcher {
haystack,
needle,
searcher: StrSearcherImpl::Byte(ByteNeedle { b, position: 0, end: haystack.len() }),
}
} else {
StrSearcher {
haystack,
Expand Down Expand Up @@ -1140,6 +1157,23 @@ unsafe impl<'a, 'b> Searcher<'a> for StrSearcher<'a, 'b> {
}
}
}
StrSearcherImpl::Byte(ref mut searcher) => {
let bytes = self.haystack.as_bytes();
let pos = searcher.position;
if pos >= bytes.len() {
return SearchStep::Done;
}
if bytes[pos] == searcher.b {
searcher.position = pos + 1;
SearchStep::Match(pos, pos + 1)
} else {
// `pos` is always on a char boundary, so this rejects
// exactly the char starting at `pos`.
let end = self.haystack.ceil_char_boundary(pos + 1);
searcher.position = end;
SearchStep::Reject(pos, end)
}
}
StrSearcherImpl::TwoWay(ref mut searcher) => {
// TwoWaySearcher produces valid *Match* indices that split at char boundaries
// as long as it does correct matching and that haystack and needle are
Expand All @@ -1155,11 +1189,9 @@ unsafe impl<'a, 'b> Searcher<'a> for StrSearcher<'a, 'b> {
self.needle.as_bytes(),
is_long,
) {
SearchStep::Reject(a, mut b) => {
SearchStep::Reject(a, b) => {
// skip to next char boundary
while !self.haystack.is_char_boundary(b) {
b += 1;
}
let b = self.haystack.ceil_char_boundary(b);
searcher.position = cmp::max(b, searcher.position);
SearchStep::Reject(a, b)
}
Expand All @@ -1179,6 +1211,23 @@ unsafe impl<'a, 'b> Searcher<'a> for StrSearcher<'a, 'b> {
SearchStep::Reject(..) => {}
}
},
StrSearcherImpl::Byte(ref mut searcher) => {
let bytes = self.haystack.as_bytes();
if searcher.position >= bytes.len() {
return None;
}
match memchr::memchr(searcher.b, &bytes[searcher.position..]) {
Some(i) => {
let pos = searcher.position + i;
searcher.position = pos + 1;
Some((pos, pos + 1))
}
None => {
searcher.position = bytes.len();
None
}
}
}
StrSearcherImpl::TwoWay(ref mut searcher) => {
let is_long = searcher.memory == usize::MAX;
// write out `true` and `false` cases to encourage the compiler
Expand Down Expand Up @@ -1224,6 +1273,21 @@ unsafe impl<'a, 'b> ReverseSearcher<'a> for StrSearcher<'a, 'b> {
}
}
}
StrSearcherImpl::Byte(ref mut searcher) => {
let end = searcher.end;
if end == 0 {
return SearchStep::Done;
}
let bytes = self.haystack.as_bytes();
if bytes[end - 1] == searcher.b {
searcher.end = end - 1;
SearchStep::Match(end - 1, end)
} else {
let start = self.haystack.floor_char_boundary(end - 1);
searcher.end = start;
SearchStep::Reject(start, end)
}
}
StrSearcherImpl::TwoWay(ref mut searcher) => {
if searcher.end == 0 {
return SearchStep::Done;
Expand All @@ -1234,11 +1298,9 @@ unsafe impl<'a, 'b> ReverseSearcher<'a> for StrSearcher<'a, 'b> {
self.needle.as_bytes(),
is_long,
) {
SearchStep::Reject(mut a, b) => {
// skip to next char boundary
while !self.haystack.is_char_boundary(a) {
a -= 1;
}
SearchStep::Reject(a, b) => {
// skip to previous char boundary
let a = self.haystack.floor_char_boundary(a);
searcher.end = cmp::min(a, searcher.end);
SearchStep::Reject(a, b)
}
Expand All @@ -1258,6 +1320,22 @@ unsafe impl<'a, 'b> ReverseSearcher<'a> for StrSearcher<'a, 'b> {
SearchStep::Reject(..) => {}
}
},
StrSearcherImpl::Byte(ref mut searcher) => {
if searcher.end == 0 {
return None;
}
let bytes = self.haystack.as_bytes();
match memchr::memrchr(searcher.b, &bytes[..searcher.end]) {
Some(i) => {
searcher.end = i;
Some((i, i + 1))
}
None => {
searcher.end = 0;
None
}
}
}
StrSearcherImpl::TwoWay(ref mut searcher) => {
let is_long = searcher.memory == usize::MAX;
// write out `true` and `false`, like `next_match`
Expand Down
152 changes: 152 additions & 0 deletions library/coretests/benches/pattern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,155 @@ fn rfind_str_worst_case(b: &mut Bencher) {
b.bytes = haystack.len() as u64;
b.iter(|| black_box(haystack.rfind("the english language")))
}

/// 64 KiB of text that does not contain the needle.
fn haystack_without_needle() -> String {
"abcdefgh".repeat(8 * 1024)
}

#[bench]
fn find_1byte_str_long_nomatch(b: &mut Bencher) {
let s = haystack_without_needle();
let haystack = black_box(s.as_str());
b.bytes = haystack.len() as u64;
b.iter(|| black_box(haystack.find(",")))
}

#[bench]
fn find_char_long_nomatch(b: &mut Bencher) {
let s = haystack_without_needle();
let haystack = black_box(s.as_str());
b.bytes = haystack.len() as u64;
b.iter(|| black_box(haystack.find(',')))
}

#[bench]
fn find_1byte_str_long_match_end(b: &mut Bencher) {
let mut s = haystack_without_needle();
s.push(',');
let haystack = black_box(s.as_str());
b.bytes = haystack.len() as u64;
b.iter(|| black_box(haystack.find(",")))
}

#[bench]
fn find_char_long_match_end(b: &mut Bencher) {
let mut s = haystack_without_needle();
s.push(',');
let haystack = black_box(s.as_str());
b.bytes = haystack.len() as u64;
b.iter(|| black_box(haystack.find(',')))
}

#[bench]
fn rfind_1byte_str_long_nomatch(b: &mut Bencher) {
let s = haystack_without_needle();
let haystack = black_box(s.as_str());
b.bytes = haystack.len() as u64;
b.iter(|| black_box(haystack.rfind(",")))
}

#[bench]
fn rfind_char_long_nomatch(b: &mut Bencher) {
let s = haystack_without_needle();
let haystack = black_box(s.as_str());
b.bytes = haystack.len() as u64;
b.iter(|| black_box(haystack.rfind(',')))
}

#[bench]
fn find_1byte_str_early_return(b: &mut Bencher) {
let mut s = String::from("abcdefg,");
s.push_str(&haystack_without_needle());
let haystack = black_box(s.as_str());
b.iter(|| {
for _ in 0..1024 {
black_box(black_box(haystack).find(","));
}
})
}

#[bench]
fn find_char_early_return(b: &mut Bencher) {
let mut s = String::from("abcdefg,");
s.push_str(&haystack_without_needle());
let haystack = black_box(s.as_str());
b.iter(|| {
for _ in 0..1024 {
black_box(black_box(haystack).find(','));
}
})
}

// Short haystacks measure searcher construction overhead as much as the scan.
#[bench]
fn find_1byte_str_short_haystack(b: &mut Bencher) {
let haystack = black_box("abcdefg,ijklmno");
b.iter(|| {
for _ in 0..1024 {
black_box(black_box(haystack).find(","));
}
})
}

#[bench]
fn find_char_short_haystack(b: &mut Bencher) {
let haystack = black_box("abcdefg,ijklmno");
b.iter(|| {
for _ in 0..1024 {
black_box(black_box(haystack).find(','));
}
})
}

// Match-dense input: a match every third byte, the worst case for any
// skip-ahead scheme since there is nothing to skip.
#[bench]
fn split_1byte_str_dense(b: &mut Bencher) {
let s = "ab,".repeat(8 * 1024);
let haystack = black_box(s.as_str());
b.bytes = haystack.len() as u64;
b.iter(|| black_box(haystack.split(",").count()))
}

#[bench]
fn split_char_dense(b: &mut Bencher) {
let s = "ab,".repeat(8 * 1024);
let haystack = black_box(s.as_str());
b.bytes = haystack.len() as u64;
b.iter(|| black_box(haystack.split(',').count()))
}

// A match every 65 bytes, resembling line splitting.
#[bench]
fn split_1byte_str_sparse(b: &mut Bencher) {
let s = format!("{},", "abcdefgh".repeat(8)).repeat(1000);
let haystack = black_box(s.as_str());
b.bytes = haystack.len() as u64;
b.iter(|| black_box(haystack.split(",").count()))
}

#[bench]
fn split_char_sparse(b: &mut Bencher) {
let s = format!("{},", "abcdefgh".repeat(8)).repeat(1000);
let haystack = black_box(s.as_str());
b.bytes = haystack.len() as u64;
b.iter(|| black_box(haystack.split(',').count()))
}

// Haystack dominated by multi-byte chars, ASCII needle.
#[bench]
fn split_1byte_str_multibyte_haystack(b: &mut Bencher) {
let s = "\u{251c}\u{2500}\u{2500} ".repeat(8 * 1024);
let haystack = black_box(s.as_str());
b.bytes = haystack.len() as u64;
b.iter(|| black_box(haystack.split(" ").count()))
}

#[bench]
fn split_char_multibyte_haystack(b: &mut Bencher) {
let s = "\u{251c}\u{2500}\u{2500} ".repeat(8 * 1024);
let haystack = black_box(s.as_str());
b.bytes = haystack.len() as u64;
b.iter(|| black_box(haystack.split(' ').count()))
}
Loading