-
Notifications
You must be signed in to change notification settings - Fork 13.4k
char_indices
is slower than while loop over ascii Chars
, but faster for unicode
#141855
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
@rustbot: label +I-slow |
Remarkably, if you remove the unused second option in the EscapeError enum, namely BareCarriageReturnInRawString, then the results become even more different:
|
I was curious so I tried this on my machine and got opposite results:
(on |
@solson Interesting! I'm running on Linux x86_64. You? In fact adding unicode benches I get the opposite as well: #[bench]
fn bench_check_raw_str_unicode_while(b: &mut test::Bencher) {
bench_check_raw_str_while(b, "🦀", '🦀');
}
#[bench]
fn bench_check_raw_str_unicode_char_indices(b: &mut test::Bencher) {
bench_check_raw_str_char_indices(b, "🦀", '🦀');
}
|
Yep, Linux x86_64 here too.
|
After updating to the new nightly (1.89.0-nightly (4d08223 2025-05-31)), I indeed get different results:
mostly because the while variant has become slower in this version??? |
On rustc 1.89.0-nightly (59aa1e8 2025-06-03) results are back to "normal":
|
char_indices
is slower than manual while loopchar_indices
is slower than while loop over ascii Chars
, but faster for unicode
Now if I add strings which alternate between ascii and non-ascii, and change the benchmark code to accomodate that, like so: #![feature(test)]
extern crate test;
use bench_char_indices::*;
use std::iter::repeat_n;
const LEN: usize = 10_000;
macro_rules! fn_bench_check_raw {
($name:ident, $unit:ty, $check_raw:ident) => {
fn $name(b: &mut test::Bencher, s: &str, expected: Vec<$unit>) {
let input: String = test::black_box(repeat_n(s, LEN).collect());
assert_eq!(input.len(), LEN * s.len());
b.iter(|| {
let mut output = vec![];
$check_raw(&input, |range, res| output.push((range, res)));
assert_eq!(output.len(), LEN * s.chars().count());
for ((i, &e), (p, c)) in expected.iter().enumerate().zip(s.char_indices()) {
assert_eq!(output[i], ((p..p + c.len_utf8()), Ok(e)));
}
});
}
};
}
fn_bench_check_raw!(bench_check_raw_str_while, char, check_raw_str_while);
fn_bench_check_raw!(
bench_check_raw_str_char_indices,
char,
check_raw_str_char_indices
);
#[bench]
fn bench_check_raw_str_ascii_while(b: &mut test::Bencher) {
bench_check_raw_str_while(b, "a", repeat_n('a', LEN).collect());
}
#[bench]
fn bench_check_raw_str_ascii_char_indices(b: &mut test::Bencher) {
bench_check_raw_str_char_indices(b, "a", repeat_n('a', LEN).collect());
}
#[bench]
fn bench_check_raw_str_unicode_while(b: &mut test::Bencher) {
bench_check_raw_str_while(b, "🦀", repeat_n('🦀', LEN).collect());
}
#[bench]
fn bench_check_raw_str_unicode_char_indices(b: &mut test::Bencher) {
bench_check_raw_str_char_indices(b, "🦀", repeat_n('🦀', LEN).collect());
}
#[bench]
fn bench_check_raw_str_mixed_while(b: &mut test::Bencher) {
bench_check_raw_str_while(
b,
"a🦀",
(0..2 * LEN)
.map(|i| if i % 2 == 0 { 'a' } else { '🦀' })
.collect(),
);
}
#[bench]
fn bench_check_raw_str_mixed_char_indices(b: &mut test::Bencher) {
bench_check_raw_str_char_indices(
b,
"a🦀",
(0..2 * LEN)
.map(|i| if i % 2 == 0 { 'a' } else { '🦀' })
.collect(),
);
} then I get these puzzling results:
Note how now |
I'd be curious to compare the length of the generated assembly, as longer asm with less jumps tends to perform better in microbenchmarks but suffers in real application code because of icache congestion. |
I tried this code:
with these benches:
I expected to see this happen: NO performance difference
Instead, there is a more than 10% difference:
Tested on: rustc 1.88.0-nightly (2e6882a 2025-05-05)
The text was updated successfully, but these errors were encountered: