Skip to content

Commit 86bb0db

Browse files
authored
Merge pull request #6 from Lips7/perf/defer-string-alloc
perf: defer String allocation in ProcessMatcher replace/delete
2 parents df9f731 + 3bec703 commit 86bb0db

1 file changed

Lines changed: 95 additions & 62 deletions

File tree

matcher_rs/src/process/process_matcher.rs

Lines changed: 95 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -212,62 +212,83 @@ impl ProcessMatcher {
212212
text: &'a str,
213213
process_replace_list: &[&str],
214214
) -> (bool, Cow<'a, str>) {
215-
let mut result = String::with_capacity(text.len());
216-
let mut last_end = 0;
217215
match self {
218216
#[cfg(not(feature = "dfa"))]
219217
ProcessMatcher::LeftMost(ac) => {
220-
for mat in ac.leftmost_find_iter(text) {
221-
// SAFETY: `last_end` corresponds to the end of the previous match (or 0), and `mat.start()`
222-
// is strictly greater than or equal to `last_end`. The match boundaries guaranteed by
218+
let mut iter = ac.leftmost_find_iter(text);
219+
if let Some(first_mat) = iter.next() {
220+
let mut result = String::with_capacity(text.len());
221+
// SAFETY: `first_mat.start()` is strictly greater than or equal to 0. The match boundaries guaranteed by
223222
// the automaton always fall exactly on valid UTF-8 character boundaries.
224-
result.push_str(unsafe { text.get_unchecked(last_end..mat.start()) });
225-
// SAFETY: The `mat.value()` returned is exactly an index mapped 1:1 during automaton construction
223+
result.push_str(unsafe { text.get_unchecked(0..first_mat.start()) });
224+
// SAFETY: The `first_mat.value()` returned is exactly an index mapped 1:1 during automaton construction
226225
// with `process_replace_list`, so it will never go naturally out of bounds.
227226
result.push_str(unsafe {
228-
process_replace_list.get_unchecked(mat.value() as usize)
227+
process_replace_list.get_unchecked(first_mat.value() as usize)
229228
});
230-
last_end = mat.end();
229+
let mut last_end = first_mat.end();
230+
for mat in iter {
231+
// SAFETY: `last_end` corresponds to the end of the previous match (or 0), and `mat.start()`
232+
// is strictly greater than or equal to `last_end`. The match boundaries guaranteed by
233+
// the automaton always fall exactly on valid UTF-8 character boundaries.
234+
result.push_str(unsafe { text.get_unchecked(last_end..mat.start()) });
235+
// SAFETY: The `mat.value()` returned is exactly an index mapped 1:1 during automaton construction
236+
// with `process_replace_list`, so it will never go naturally out of bounds.
237+
result.push_str(unsafe {
238+
process_replace_list.get_unchecked(mat.value() as usize)
239+
});
240+
last_end = mat.end();
241+
}
242+
// SAFETY: `last_end` falls exactly on the end of the final matching pattern,
243+
// naturally guaranteeing it exists on a valid UTF-8 character boundary.
244+
result.push_str(unsafe { text.get_unchecked(last_end..) });
245+
return (true, Cow::Owned(result));
231246
}
232247
}
233248
ProcessMatcher::Chinese(ac) => {
234-
for mat in ac.find_iter(text) {
235-
// SAFETY: `last_end` corresponds to the end of the previous match (or 0), and `mat.start()`
236-
// is strictly greater than or equal to `last_end`. The match boundaries guaranteed by
237-
// the automaton always fall exactly on valid UTF-8 character boundaries.
238-
result.push_str(unsafe { text.get_unchecked(last_end..mat.start()) });
239-
// SAFETY: The `mat.value()` returned is exactly an index mapped 1:1 during automaton construction
240-
// with `process_replace_list`, so it will never go naturally out of bounds.
249+
let mut iter = ac.find_iter(text);
250+
if let Some(first_mat) = iter.next() {
251+
let mut result = String::with_capacity(text.len());
252+
result.push_str(unsafe { text.get_unchecked(0..first_mat.start()) });
241253
result.push_str(unsafe {
242-
process_replace_list.get_unchecked(mat.value() as usize)
254+
process_replace_list.get_unchecked(first_mat.value() as usize)
243255
});
244-
last_end = mat.end();
256+
let mut last_end = first_mat.end();
257+
for mat in iter {
258+
result.push_str(unsafe { text.get_unchecked(last_end..mat.start()) });
259+
result.push_str(unsafe {
260+
process_replace_list.get_unchecked(mat.value() as usize)
261+
});
262+
last_end = mat.end();
263+
}
264+
result.push_str(unsafe { text.get_unchecked(last_end..) });
265+
return (true, Cow::Owned(result));
245266
}
246267
}
247268
ProcessMatcher::Others(ac) => {
248-
for mat in ac.find_iter(text) {
249-
// SAFETY: `last_end` corresponds to the end of the previous match (or 0), and `mat.start()`
250-
// is strictly greater than or equal to `last_end`. The match boundaries guaranteed by
251-
// the automaton always fall exactly on valid UTF-8 character boundaries.
252-
result.push_str(unsafe { text.get_unchecked(last_end..mat.start()) });
269+
let mut iter = ac.find_iter(text);
270+
if let Some(first_mat) = iter.next() {
271+
let mut result = String::with_capacity(text.len());
272+
result.push_str(unsafe { text.get_unchecked(0..first_mat.start()) });
253273
// SAFETY: The pattern ID returned is an index bounded tightly bounded by
254274
// `process_replace_list` mapped directly from pattern initialization.
255275
result.push_str(unsafe {
256-
process_replace_list.get_unchecked(mat.pattern().as_usize())
276+
process_replace_list.get_unchecked(first_mat.pattern().as_usize())
257277
});
258-
last_end = mat.end();
278+
let mut last_end = first_mat.end();
279+
for mat in iter {
280+
result.push_str(unsafe { text.get_unchecked(last_end..mat.start()) });
281+
result.push_str(unsafe {
282+
process_replace_list.get_unchecked(mat.pattern().as_usize())
283+
});
284+
last_end = mat.end();
285+
}
286+
result.push_str(unsafe { text.get_unchecked(last_end..) });
287+
return (true, Cow::Owned(result));
259288
}
260289
}
261290
}
262-
263-
if last_end > 0 {
264-
// SAFETY: `last_end` falls exactly on the end of the final matching pattern,
265-
// naturally guaranteeing it exists on a valid UTF-8 character boundary.
266-
result.push_str(unsafe { text.get_unchecked(last_end..) });
267-
(true, Cow::Owned(result))
268-
} else {
269-
(false, Cow::Borrowed(text))
270-
}
291+
(false, Cow::Borrowed(text))
271292
}
272293

273294
/// Deletes all matched patterns in the provided text.
@@ -293,47 +314,59 @@ impl ProcessMatcher {
293314
/// not to fail based on the matcher's behavior.
294315
#[inline(always)]
295316
pub fn delete_all<'a>(&self, text: &'a str) -> (bool, Cow<'a, str>) {
296-
let mut result = String::with_capacity(text.len());
297-
let mut last_end = 0;
298317
match self {
299318
#[cfg(not(feature = "dfa"))]
300319
ProcessMatcher::LeftMost(ac) => {
301-
for mat in ac.leftmost_find_iter(text) {
302-
// SAFETY: `last_end` corresponds to the end of the previous match (or 0), and `mat.start()`
303-
// is strictly greater than or equal to `last_end`. The match boundaries guaranteed by
320+
let mut iter = ac.leftmost_find_iter(text);
321+
if let Some(first_mat) = iter.next() {
322+
let mut result = String::with_capacity(text.len());
323+
// SAFETY: `first_mat.start()` is strictly greater than or equal to 0. The match boundaries guaranteed by
304324
// the automaton always fall exactly on valid UTF-8 character boundaries.
305-
result.push_str(unsafe { text.get_unchecked(last_end..mat.start()) });
306-
last_end = mat.end();
325+
result.push_str(unsafe { text.get_unchecked(0..first_mat.start()) });
326+
let mut last_end = first_mat.end();
327+
for mat in iter {
328+
// SAFETY: `last_end` corresponds to the end of the previous match (or 0), and `mat.start()`
329+
// is strictly greater than or equal to `last_end`. The match boundaries guaranteed by
330+
// the automaton always fall exactly on valid UTF-8 character boundaries.
331+
result.push_str(unsafe { text.get_unchecked(last_end..mat.start()) });
332+
last_end = mat.end();
333+
}
334+
// SAFETY: `last_end` falls exactly on the end of the final matching pattern,
335+
// naturally guaranteeing it exists on a valid UTF-8 character boundary.
336+
result.push_str(unsafe { text.get_unchecked(last_end..) });
337+
return (true, Cow::Owned(result));
307338
}
308339
}
309340
ProcessMatcher::Chinese(ac) => {
310-
for mat in ac.find_iter(text) {
311-
// SAFETY: `last_end` corresponds to the end of the previous match (or 0), and `mat.start()`
312-
// is strictly greater than or equal to `last_end`. The match boundaries guaranteed by
313-
// the automaton always fall exactly on valid UTF-8 character boundaries.
314-
result.push_str(unsafe { text.get_unchecked(last_end..mat.start()) });
315-
last_end = mat.end();
341+
let mut iter = ac.find_iter(text);
342+
if let Some(first_mat) = iter.next() {
343+
let mut result = String::with_capacity(text.len());
344+
result.push_str(unsafe { text.get_unchecked(0..first_mat.start()) });
345+
let mut last_end = first_mat.end();
346+
for mat in iter {
347+
result.push_str(unsafe { text.get_unchecked(last_end..mat.start()) });
348+
last_end = mat.end();
349+
}
350+
result.push_str(unsafe { text.get_unchecked(last_end..) });
351+
return (true, Cow::Owned(result));
316352
}
317353
}
318354
ProcessMatcher::Others(ac) => {
319-
for mat in ac.find_iter(text) {
320-
// SAFETY: `last_end` corresponds to the end of the previous match (or 0), and `mat.start()`
321-
// is strictly greater than or equal to `last_end`. The match boundaries guaranteed by
322-
// the automaton always fall exactly on valid UTF-8 character boundaries.
323-
result.push_str(unsafe { text.get_unchecked(last_end..mat.start()) });
324-
last_end = mat.end();
355+
let mut iter = ac.find_iter(text);
356+
if let Some(first_mat) = iter.next() {
357+
let mut result = String::with_capacity(text.len());
358+
result.push_str(unsafe { text.get_unchecked(0..first_mat.start()) });
359+
let mut last_end = first_mat.end();
360+
for mat in iter {
361+
result.push_str(unsafe { text.get_unchecked(last_end..mat.start()) });
362+
last_end = mat.end();
363+
}
364+
result.push_str(unsafe { text.get_unchecked(last_end..) });
365+
return (true, Cow::Owned(result));
325366
}
326367
}
327368
}
328-
329-
if last_end > 0 {
330-
// SAFETY: `last_end` falls exactly on the end of the final matching pattern,
331-
// naturally guaranteeing it exists on a valid UTF-8 character boundary.
332-
result.push_str(unsafe { text.get_unchecked(last_end..) });
333-
(true, Cow::Owned(result))
334-
} else {
335-
(false, Cow::Borrowed(text))
336-
}
369+
(false, Cow::Borrowed(text))
337370
}
338371
}
339372

0 commit comments

Comments
 (0)