Skip to content

Commit e51d134

Browse files
committed
Improve: memmem::Finder over ::find_iter
1 parent faae9fa commit e51d134

2 files changed

Lines changed: 45 additions & 37 deletions

File tree

README.md

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -98,32 +98,32 @@ Most of the time, programmers don't think about replacing the `str::find` method
9898
In many languages it's offloaded to the C standard library [`memmem`](https://man7.org/linux/man-pages/man3/memmem.3.html) or [`strstr`](https://en.cppreference.com/w/c/string/byte/strstr) for `NULL`-terminated strings.
9999
The C standard library is, however, also implemented by humans, and a better solution can be created.
100100

101-
| Library | Short Words | Long Lines |
102-
| ------------------- | --------------: | --------------: |
103-
| Rust 🦀 | | |
104-
| `std::str::find` | 9.48 GiB/s | 10.88 GiB/s |
105-
| `memmem::find` | 9.51 GiB/s | 10.83 GiB/s |
106-
| `stringzilla::find` | __10.45 GiB/s__ | __10.89 GiB/s__ |
107-
| | | |
108-
| Python 🐍 | | |
109-
| `str.find` | 1.05 GiB/s | 1.23 GiB/s |
110-
| `stringzilla.find` | __10.82 GiB/s__ | __11.79 GiB/s__ |
111-
112-
> Higher-throughput evaluation with `memmem` is possible, if the "matcher" object is reused to iterate through the string instead of constructing a new one for each search.
101+
| Library | Short Word Queries | Long Line Queries |
102+
| ------------------- | -----------------: | ----------------: |
103+
| Rust 🦀 | | |
104+
| `std::str::find` | 9.45 GiB/s | 10.88 GiB/s |
105+
| `memmem::find` | 9.48 GiB/s | 10.83 GiB/s |
106+
| `memmem::Finder` | 9.51 GiB/s | __10.99 GiB/s__ |
107+
| `stringzilla::find` | __10.51 GiB/s__ | 10.82 GiB/s |
108+
| | | |
109+
| Python 🐍 | | |
110+
| `str.find` | 1.05 GiB/s | 1.23 GiB/s |
111+
| `stringzilla.find` | __10.82 GiB/s__ | __11.79 GiB/s__ |
113112

114113
Interestingly, the reverse order search is almost never implemented in SIMD, assuming fewer people ever need it.
115114
Still, those are provided by StringZilla mostly for parsing tasks and feature parity.
116115

117-
| Library | Short Words | Long Lines |
118-
| -------------------- | -------------: | --------------: |
119-
| Rust 🦀 | | |
120-
| `std::str::rfind` | 2.96 GiB/s | 3.65 GiB/s |
121-
| `memmem::rfind` | 2.95 GiB/s | 3.71 GiB/s |
122-
| `stringzilla::rfind` | __9.78 GiB/s__ | __10.43 GiB/s__ |
123-
| | | |
124-
| Python 🐍 | | |
125-
| `str.rfind` | 1.54 GiB/s | 3.84 GiB/s |
126-
| `stringzilla.rfind` | __7.15 GiB/s__ | __11.56 GiB/s__ |
116+
| Library | Short Word Queries | Long Line Queries |
117+
| -------------------- | -----------------: | ----------------: |
118+
| Rust 🦀 | | |
119+
| `std::str::rfind` | 2.72 GiB/s | 5.94 GiB/s |
120+
| `memmem::rfind` | 2.70 GiB/s | 5.90 GiB/s |
121+
| `memmem::FinderRev` | 2.79 GiB/s | 5.81 GiB/s |
122+
| `stringzilla::rfind` | __10.34 GiB/s__ | __10.66 GiB/s__ |
123+
| | | |
124+
| Python 🐍 | | |
125+
| `str.rfind` | 1.54 GiB/s | 3.84 GiB/s |
126+
| `stringzilla.rfind` | __7.15 GiB/s__ | __11.56 GiB/s__ |
127127

128128

129129
## Byte-Set Search

bench_find.rs

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -126,25 +126,27 @@ fn bench_substring_forward(
126126
})
127127
});
128128

129-
// Benchmark for default `std::str::find` forward search.
129+
// Benchmark for `memmem::Finder` forward search with pre-constructed matcher.
130130
let mut tokens = needles.iter().cycle();
131-
g.bench_function("std::str::find", |b| {
131+
g.bench_function("memmem::Finder", |b| {
132132
b.iter(|| {
133133
let token = black_box(*tokens.next().unwrap());
134-
let mut pos = 0;
135-
while let Some(found) = haystack[pos..].find(token) {
134+
let finder = memmem::Finder::new(token);
135+
let mut pos: usize = 0;
136+
while let Some(found) = finder.find(&haystack[pos..]) {
136137
pos += found + token.len();
137138
}
138139
})
139140
});
140141

141-
// Benchmark for `memmem::find_iter` forward search using a cycle iterator.
142+
// Benchmark for default `std::str::find` forward search.
142143
let mut tokens = needles.iter().cycle();
143-
g.bench_function("memmem::find_iter", |b| {
144+
g.bench_function("std::str::find", |b| {
144145
b.iter(|| {
145146
let token = black_box(*tokens.next().unwrap());
146-
for match_ in memmem::find_iter(haystack, token) {
147-
black_box(match_);
147+
let mut pos = 0;
148+
while let Some(found) = haystack[pos..].find(token) {
149+
pos += found + token.len();
148150
}
149151
})
150152
});
@@ -190,14 +192,15 @@ fn bench_substring_backward(
190192
})
191193
});
192194

193-
// Benchmark for default `std::str::rfind` backward search.
195+
// Benchmark for `memmem::FinderRev` backward search with pre-constructed matcher.
194196
let mut tokens = needles.iter().cycle();
195-
g.bench_function("std::str::rfind", |b| {
197+
g.bench_function("memmem::FinderRev", |b| {
196198
b.iter(|| {
197199
let token = black_box(*tokens.next().unwrap());
200+
let finder = memmem::FinderRev::new(token);
198201
let mut pos: Option<usize> = Some(haystack.len());
199202
while let Some(end) = pos {
200-
if let Some(found) = haystack[..end].rfind(token) {
203+
if let Some(found) = finder.rfind(&haystack[..end]) {
201204
pos = Some(found);
202205
} else {
203206
break;
@@ -206,13 +209,18 @@ fn bench_substring_backward(
206209
})
207210
});
208211

209-
// Benchmark for `memmem::rfind_iter` forward search using a cycle iterator.
212+
// Benchmark for default `std::str::rfind` backward search.
210213
let mut tokens = needles.iter().cycle();
211-
g.bench_function("memmem::rfind_iter", |b| {
214+
g.bench_function("std::str::rfind", |b| {
212215
b.iter(|| {
213216
let token = black_box(*tokens.next().unwrap());
214-
for match_ in memmem::rfind_iter(haystack, token) {
215-
black_box(match_);
217+
let mut pos: Option<usize> = Some(haystack.len());
218+
while let Some(end) = pos {
219+
if let Some(found) = haystack[..end].rfind(token) {
220+
pos = Some(found);
221+
} else {
222+
break;
223+
}
216224
}
217225
})
218226
});

0 commit comments

Comments
 (0)