Skip to content

Commit 104784a

Browse files
committed
Optimise replacen_smolstr for single ascii replace
1 parent 08a9dc8 commit 104784a

File tree

3 files changed

+44
-0
lines changed

3 files changed

+44
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
- Optimise `StrExt::to_ascii_lowercase_smolstr`, `StrExt::to_ascii_uppercase_smolstr`
66
~2x speedup inline, ~4-22x for heap.
77
- Optimise `StrExt::to_lowercase_smolstr`, `StrExt::to_uppercase_smolstr` ~2x speedup inline, ~5-50x for heap.
8+
- Optimise `StrExt::replace_smolstr`, `StrExt::replacen_smolstr` for single ascii replace.
9+
~3x speedup inline, ~1.8x for heap (len=50).
810

911
## 0.3.2 - 2024-10-23
1012

src/lib.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,13 @@ impl StrExt for str {
715715

716716
#[inline]
717717
fn replacen_smolstr(&self, from: &str, to: &str, count: usize) -> SmolStr {
718+
// Fast path for replacing a single ASCII character with another inline.
719+
if let [from_u8] = from.as_bytes() {
720+
if let [to_u8] = to.as_bytes() {
721+
return replacen_1_ascii(self, *from_u8, *to_u8, count);
722+
}
723+
}
724+
718725
let mut result = SmolStrBuilder::new();
719726
let mut last_end = 0;
720727
for (start, part) in self.match_indices(from).take(count) {
@@ -731,6 +738,34 @@ impl StrExt for str {
731738
}
732739
}
733740

741+
#[inline]
742+
fn replacen_1_ascii(src: &str, from: u8, to: u8, count: usize) -> SmolStr {
743+
let mut replaced = 0;
744+
let mut ascii_replace = |b: &u8| {
745+
if *b == from && replaced != count {
746+
replaced += 1;
747+
to
748+
} else {
749+
*b
750+
}
751+
};
752+
if src.len() <= INLINE_CAP {
753+
let mut buf = [0u8; INLINE_CAP];
754+
for (idx, b) in src.as_bytes().iter().enumerate() {
755+
buf[idx] = ascii_replace(b);
756+
}
757+
SmolStr(Repr::Inline {
758+
// SAFETY: `len` is in bounds
759+
len: unsafe { InlineSize::transmute_from_u8(src.len() as u8) },
760+
buf,
761+
})
762+
} else {
763+
let out = src.as_bytes().iter().map(ascii_replace).collect();
764+
// SAFETY: We replaced ascii with ascii on valid utf8 strings.
765+
unsafe { String::from_utf8_unchecked(out).into() }
766+
}
767+
}
768+
734769
/// Inline version of std fn `convert_while_ascii`. `s` must have len <= 23.
735770
#[inline]
736771
fn inline_convert_while_ascii(s: &str, convert: fn(&u8) -> u8) -> ([u8; INLINE_CAP], &str) {

tests/test.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,13 @@ mod test_str_ext {
389389
assert_eq!(result, "foo_dor_baz");
390390
assert!(!result.is_heap_allocated());
391391
}
392+
393+
#[test]
394+
fn replacen_1_ascii() {
395+
let result = "foo_bar_baz".replacen_smolstr("o", "u", 1);
396+
assert_eq!(result, "fuo_bar_baz");
397+
assert!(!result.is_heap_allocated());
398+
}
392399
}
393400

394401
#[cfg(feature = "borsh")]

0 commit comments

Comments
 (0)