-
Notifications
You must be signed in to change notification settings - Fork 13.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Rollup merge of #74066 - thomcc:optimize-is-ascii, r=nagisa
Optimize is_ascii for str and [u8]. This optimizes the `is_ascii` function for `[u8]` and `str`. I've been surprised this wasn't done for a while, so I just did it. Benchmarks comparing before/after look like: ``` test ascii::long_readonly::is_ascii_slice_iter_all ... bench: 174 ns/iter (+/- 79) = 40172 MB/s test ascii::long_readonly::is_ascii_slice_libcore ... bench: 16 ns/iter (+/- 5) = 436875 MB/s test ascii::medium_readonly::is_ascii_slice_iter_all ... bench: 12 ns/iter (+/- 3) = 2666 MB/s test ascii::medium_readonly::is_ascii_slice_libcore ... bench: 2 ns/iter (+/- 0) = 16000 MB/s test ascii::short_readonly::is_ascii_slice_iter_all ... bench: 3 ns/iter (+/- 0) = 2333 MB/s test ascii::short_readonly::is_ascii_slice_libcore ... bench: 4 ns/iter (+/- 0) = 1750 MB/s ``` (Taken on a x86_64 macbook 2.9 GHz Intel Core i9 with 6 cores) Where `is_ascii_slice_iter_all` is the old version, and `is_ascii_slice_libcore` is the new. I tried to document the code well, so hopefully it's understandable. It has fairly exhaustive tests ensuring size/align doesn't get violated -- because `miri` doesn't really help a lot for this sort of code right now, I tried to `debug_assert` all the safety invariants I'm depending on. (Of course, none of them are required for correctness or soundness -- just allows us to test that this sort of pointer manipulation is sound and such). Anyway, thanks. Let me know if you have questions/desired changes.
- Loading branch information
Showing
5 changed files
with
242 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
use super::{LONG, MEDIUM, SHORT}; | ||
use test::black_box; | ||
use test::Bencher; | ||
|
||
macro_rules! benches { | ||
($( fn $name: ident($arg: ident: &[u8]) $body: block )+) => { | ||
benches!(mod short SHORT[..] $($name $arg $body)+); | ||
benches!(mod medium MEDIUM[..] $($name $arg $body)+); | ||
benches!(mod long LONG[..] $($name $arg $body)+); | ||
// Ensure we benchmark cases where the functions are called with strings | ||
// that are not perfectly aligned or have a length which is not a | ||
// multiple of size_of::<usize>() (or both) | ||
benches!(mod unaligned_head MEDIUM[1..] $($name $arg $body)+); | ||
benches!(mod unaligned_tail MEDIUM[..(MEDIUM.len() - 1)] $($name $arg $body)+); | ||
benches!(mod unaligned_both MEDIUM[1..(MEDIUM.len() - 1)] $($name $arg $body)+); | ||
}; | ||
|
||
(mod $mod_name: ident $input: ident [$range: expr] $($name: ident $arg: ident $body: block)+) => { | ||
mod $mod_name { | ||
use super::*; | ||
$( | ||
#[bench] | ||
fn $name(bencher: &mut Bencher) { | ||
bencher.bytes = $input[$range].len() as u64; | ||
let mut vec = $input.as_bytes().to_vec(); | ||
bencher.iter(|| { | ||
let $arg: &[u8] = &black_box(&mut vec)[$range]; | ||
black_box($body) | ||
}) | ||
} | ||
)+ | ||
} | ||
}; | ||
} | ||
|
||
benches! { | ||
fn case00_libcore(bytes: &[u8]) { | ||
bytes.is_ascii() | ||
} | ||
|
||
fn case01_iter_all(bytes: &[u8]) { | ||
bytes.iter().all(|b| b.is_ascii()) | ||
} | ||
|
||
fn case02_align_to(bytes: &[u8]) { | ||
is_ascii_align_to(bytes) | ||
} | ||
|
||
fn case03_align_to_unrolled(bytes: &[u8]) { | ||
is_ascii_align_to_unrolled(bytes) | ||
} | ||
} | ||
|
||
// These are separate since it's easier to debug errors if they don't go through | ||
// macro expansion first. | ||
fn is_ascii_align_to(bytes: &[u8]) -> bool { | ||
if bytes.len() < core::mem::size_of::<usize>() { | ||
return bytes.iter().all(|b| b.is_ascii()); | ||
} | ||
// SAFETY: transmuting a sequence of `u8` to `usize` is always fine | ||
let (head, body, tail) = unsafe { bytes.align_to::<usize>() }; | ||
head.iter().all(|b| b.is_ascii()) | ||
&& body.iter().all(|w| !contains_nonascii(*w)) | ||
&& tail.iter().all(|b| b.is_ascii()) | ||
} | ||
|
||
fn is_ascii_align_to_unrolled(bytes: &[u8]) -> bool { | ||
if bytes.len() < core::mem::size_of::<usize>() { | ||
return bytes.iter().all(|b| b.is_ascii()); | ||
} | ||
// SAFETY: transmuting a sequence of `u8` to `[usize; 2]` is always fine | ||
let (head, body, tail) = unsafe { bytes.align_to::<[usize; 2]>() }; | ||
head.iter().all(|b| b.is_ascii()) | ||
&& body.iter().all(|w| !contains_nonascii(w[0] | w[1])) | ||
&& tail.iter().all(|b| b.is_ascii()) | ||
} | ||
|
||
#[inline] | ||
fn contains_nonascii(v: usize) -> bool { | ||
const NONASCII_MASK: usize = 0x80808080_80808080u64 as usize; | ||
(NONASCII_MASK & v) != 0 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters