Skip to content

Commit cc33ce6

Browse files
committed
Add String::push_with_ascii_fast_path, bench it against String::push
`String::push(&mut self, ch: char)` currently has a single code path that calls `Char::encode_utf8`. Perhaps it could be faster for ASCII `char`s, which are represented as a single byte in UTF-8. This commit leaves the method unchanged, adds a copy of it with the fast path, and adds benchmarks to compare them. Results show that the fast path very significantly improves the performance of repeatedly pushing an ASCII `char`, but does not significantly affect the performance for a non-ASCII `char` (where the fast path is not taken). Output of `make check-stage1-collections NO_REBUILD=1 PLEASE_BENCH=1 TESTNAME=string::tests::bench_push` ``` test string::tests::bench_push_char_one_byte ... bench: 59552 ns/iter (+/- 2132) = 167 MB/s test string::tests::bench_push_char_one_byte_with_fast_path ... bench: 6563 ns/iter (+/- 658) = 1523 MB/s test string::tests::bench_push_char_two_bytes ... bench: 71520 ns/iter (+/- 3541) = 279 MB/s test string::tests::bench_push_char_two_bytes_with_slow_path ... bench: 71452 ns/iter (+/- 4202) = 279 MB/s test string::tests::bench_push_str ... bench: 24 ns/iter (+/- 2) test string::tests::bench_push_str_one_byte ... bench: 38910 ns/iter (+/- 2477) = 257 MB/s ``` A benchmark of pushing a one-byte-long `&str` is added for comparison, but its performance [has varied a lot lately]( rust-lang#19640 (comment)). (When the input is fixed, `s.push_str("x")` could be used instead of `s.push('x')`.)
1 parent 8f51ad2 commit cc33ce6

File tree

1 file changed

+80
-0
lines changed

1 file changed

+80
-0
lines changed

src/libcollections/string.rs

+80
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,29 @@ impl String {
528528
}
529529
}
530530

531+
#[inline]
532+
fn push_with_ascii_fast_path(&mut self, ch: char) {
533+
if (ch as u32) < 0x80 {
534+
self.vec.push(ch as u8);
535+
return;
536+
}
537+
538+
let cur_len = self.len();
539+
// This may use up to 4 bytes.
540+
self.vec.reserve(4);
541+
542+
unsafe {
543+
// Attempt to not use an intermediate buffer by just pushing bytes
544+
// directly onto this string.
545+
let slice = RawSlice {
546+
data: self.vec.as_ptr().offset(cur_len as int),
547+
len: 4,
548+
};
549+
let used = ch.encode_utf8(mem::transmute(slice)).unwrap_or(0);
550+
self.vec.set_len(cur_len + used);
551+
}
552+
}
553+
531554
/// Works with the underlying buffer as a byte slice.
532555
///
533556
/// # Examples
@@ -1408,6 +1431,63 @@ mod tests {
14081431
});
14091432
}
14101433

1434+
const REPETITIONS: u64 = 10_000;
1435+
1436+
#[bench]
1437+
fn bench_push_str_one_byte(b: &mut Bencher) {
1438+
b.bytes = REPETITIONS;
1439+
b.iter(|| {
1440+
let mut r = String::new();
1441+
for _ in range(0, REPETITIONS) {
1442+
r.push_str("a")
1443+
}
1444+
});
1445+
}
1446+
1447+
#[bench]
1448+
fn bench_push_char_one_byte(b: &mut Bencher) {
1449+
b.bytes = REPETITIONS;
1450+
b.iter(|| {
1451+
let mut r = String::new();
1452+
for _ in range(0, REPETITIONS) {
1453+
r.push('a')
1454+
}
1455+
});
1456+
}
1457+
1458+
#[bench]
1459+
fn bench_push_char_one_byte_with_fast_path(b: &mut Bencher) {
1460+
b.bytes = REPETITIONS;
1461+
b.iter(|| {
1462+
let mut r = String::new();
1463+
for _ in range(0, REPETITIONS) {
1464+
r.push_with_ascii_fast_path('a')
1465+
}
1466+
});
1467+
}
1468+
1469+
#[bench]
1470+
fn bench_push_char_two_bytes(b: &mut Bencher) {
1471+
b.bytes = REPETITIONS * 2;
1472+
b.iter(|| {
1473+
let mut r = String::new();
1474+
for _ in range(0, REPETITIONS) {
1475+
r.push('â')
1476+
}
1477+
});
1478+
}
1479+
1480+
#[bench]
1481+
fn bench_push_char_two_bytes_with_slow_path(b: &mut Bencher) {
1482+
b.bytes = REPETITIONS * 2;
1483+
b.iter(|| {
1484+
let mut r = String::new();
1485+
for _ in range(0, REPETITIONS) {
1486+
r.push_with_ascii_fast_path('â')
1487+
}
1488+
});
1489+
}
1490+
14111491
#[bench]
14121492
fn from_utf8_lossy_100_ascii(b: &mut Bencher) {
14131493
let s = b"Hello there, the quick brown fox jumped over the lazy dog! \

0 commit comments

Comments
 (0)