Skip to content

Commit 6c87448

Browse files
committed
optimize Cstr/EscapeAscii display
old: ascii::bench_ascii_escape_display_mixed 17.97µs/iter +/- 204.00ns ascii::bench_ascii_escape_display_no_escape 545.00ns/iter +/- 6.00ns new: ascii::bench_ascii_escape_display_mixed 4.99µs/iter +/- 56.00ns ascii::bench_ascii_escape_display_no_escape 91.00ns/iter +/- 1.00ns
1 parent 5ea6668 commit 6c87448

File tree

7 files changed

+105
-1
lines changed

7 files changed

+105
-1
lines changed

library/core/benches/ascii.rs

+28
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ macro_rules! benches {
6363
}
6464
}
6565

66+
use std::fmt::Write;
6667
use test::black_box;
6768
use test::Bencher;
6869

@@ -351,3 +352,30 @@ static ASCII_CHARACTER_CLASS: [AsciiCharacterClass; 256] = [
351352
N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N,
352353
N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N,
353354
];
355+
356+
const ASCII_PATH: &[u8] = b"home/kyubey/rust/build/x86_64-unknown-linux-gnu/stage0/lib:/home/kyubey/workspace/rust/build/x86_64-unknown-linux-gnu/stage0-tools/release/deps";
357+
const RUST_INCANTATION: &[u8] = br#"AR_x86_64_unknown_linux_gnu="ar" CARGO_INCREMENTAL="0" CARGO_PROFILE_RELEASE_DEBUG="1" CARGO_PROFILE_RELEASE_DEBUG_ASSERTIONS="false" CARGO_PROFILE_RELEASE_OVERFLOW_CHECKS="false" CARGO_TARGET_DIR="/home/kyubey/workspace/rust/build/x86_64-unknown-linux-gnu/stage0-std" CC_x86_64_unknown_linux_gnu="cc" CFG_COMPILER_HOST_TRIPLE="x86_64-unknown-linux-gnu" CFG_RELEASE_CHANNEL="dev" CFLAGS_x86_64_unknown_linux_gnu="-ffunction-sections -fdata-sections -fPIC -m64" CXXFLAGS_x86_64_unknown_linux_gnu="-ffunction-sections -fdata-sections -fPIC -m64" CXX_x86_64_unknown_linux_gnu="c++" LD_LIBRARY_PATH="/home/kyubey/workspace/rust/build/x86_64-unknown-linux-gnu/stage0-sysroot/lib/rustlib/x86_64-unknown-linux-gnu/lib" LIBC_CHECK_CFG="1" RANLIB_x86_64_unknown_linux_gnu="ar s" REAL_LIBRARY_PATH_VAR="LD_LIBRARY_PATH" RUSTBUILD_NATIVE_DIR="/home/kyubey/workspace/rust/build/x86_64-unknown-linux-gnu/native" RUSTC="/home/kyubey/workspace/rust/build/bootstrap/debug/rustc" RUSTC_BOOTSTRAP="1" RUSTC_BREAK_ON_ICE="1" RUSTC_ERROR_METADATA_DST="/home/kyubey/workspace/rust/build/tmp/extended-error-metadata" RUSTC_FORCE_UNSTABLE="1" RUSTC_HOST_FUSE_LD_LLD="1" RUSTC_INSTALL_BINDIR="bin" RUSTC_LIBDIR="/home/kyubey/workspace/rust/build/x86_64-unknown-linux-gnu/stage0/lib" RUSTC_LINT_FLAGS="-Wrust_2018_idioms -Wunused_lifetimes -Wsemicolon_in_expressions_from_macros" RUSTC_REAL="/home/kyubey/workspace/rust/build/x86_64-unknown-linux-gnu/stage0/bin/rustc" RUSTC_SNAPSHOT="/home/kyubey/workspace/rust/build/x86_64-unknown-linux-gnu/stage0/bin/rustc" RUSTC_SNAPSHOT_LIBDIR="/home/kyubey/workspace/rust/build/x86_64-unknown-linux-gnu/stage0/lib" RUSTC_STAGE="0" RUSTC_SYSROOT="/home/kyubey/workspace/rust/build/x86_64-unknown-linux-gnu/stage0-sysroot" RUSTC_VERBOSE="0" RUSTDOC="/home/kyubey/workspace/rust/build/bootstrap/debug/rustdoc" RUSTDOCFLAGS="-C target-cpu=native --cfg=bootstrap -Csymbol-mangling-version=legacy -Zunstable-options -Zunstable-options --check-cfg=values(bootstrap) --check-cfg=values(stdarch_intel_sde) --check-cfg=values(no_fp_fmt_parse) --check-cfg=values(no_global_oom_handling) --check-cfg=values(no_rc) --check-cfg=values(no_sync) --check-cfg=values(freebsd12) --check-cfg=values(freebsd13) --check-cfg=values(backtrace_in_libstd) --check-cfg=values(target_env,\"libnx\") --check-cfg=values(target_arch,\"asmjs\",\"spirv\",\"nvptx\",\"xtensa\") -Clink-arg=-fuse-ld=lld -Clink-arg=-Wl,--threads=1 -Wrustdoc::invalid_codeblock_attributes --crate-version 1.72.0-dev -Zcrate-attr=doc(html_root_url=\"https://doc.rust-lang.org/nightly/\") -Zcrate-attr=warn(rust_2018_idioms)" RUSTDOC_FUSE_LD_LLD="1" RUSTDOC_LIBDIR="/home/kyubey/workspace/rust/build/x86_64-unknown-linux-gnu/stage0/lib" RUSTDOC_REAL="/path/to/nowhere/rustdoc/not/required" RUSTFLAGS="-C target-cpu=native --cfg=bootstrap -Csymbol-mangling-version=legacy -Zunstable-options -Zunstable-options --check-cfg=values(bootstrap) --check-cfg=values(stdarch_intel_sde) --check-cfg=values(no_fp_fmt_parse) --check-cfg=values(no_global_oom_handling) --check-cfg=values(no_rc) --check-cfg=values(no_sync) --check-cfg=values(freebsd12) --check-cfg=values(freebsd13) --check-cfg=values(backtrace_in_libstd) --check-cfg=values(target_env,\"libnx\") --check-cfg=values(target_arch,\"asmjs\",\"spirv\",\"nvptx\",\"xtensa\") -Zmacro-backtrace -Clink-args=-Wl,-z,origin -Clink-args=-Wl,-rpath,$ORIGIN/../lib -Clink-args=-fuse-ld=lld -Csplit-debuginfo=off -Cprefer-dynamic -Zinline-mir -Clto=off -Zcrate-attr=doc(html_root_url=\"https://doc.rust-lang.org/nightly/\")" RUST_COMPILER_RT_ROOT="/home/kyubey/workspace/rust/src/llvm-project/compiler-rt" RUST_TEST_THREADS="48" WINAPI_NO_BUNDLED_LIBRARIES="1" __CARGO_DEFAULT_LIB_METADATA="bootstrapstd" "/home/kyubey/workspace/rust/build/x86_64-unknown-linux-gnu/stage0/bin/cargo" "bench" "--target" "x86_64-unknown-linux-gnu" "-Zcheck-cfg=names,values,output" "-Zbinary-dep-depinfo" "-j" "48" "--features" " panic-unwind backtrace compiler-builtins-c" "--manifest-path" "/home/kyubey/workspace/rust/library/sysroot/Cargo.toml" "-p" "core" "--" "bench_ascii_escape_display" "--quiet" "-Z" "unstable-options" "--format" "json""#;
358+
359+
#[bench]
360+
fn bench_ascii_escape_display_no_escape(b: &mut Bencher) {
361+
let mut writer = String::with_capacity(8 * 1024);
362+
363+
b.iter(move || {
364+
writer.clear();
365+
let iter = ASCII_PATH.escape_ascii();
366+
write!(writer, "{}", iter).unwrap();
367+
writer.len()
368+
})
369+
}
370+
371+
#[bench]
372+
fn bench_ascii_escape_display_mixed(b: &mut Bencher) {
373+
let mut writer = String::with_capacity(8 * 1024);
374+
375+
b.iter(move || {
376+
writer.clear();
377+
let iter = RUST_INCANTATION.escape_ascii();
378+
write!(writer, "{}", iter).unwrap();
379+
writer.len()
380+
})
381+
}

library/core/src/ascii.rs

+11
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,17 @@ pub fn escape_default(c: u8) -> EscapeDefault {
9696
EscapeDefault(escape::EscapeIterInner::new(data, range))
9797
}
9898

99+
impl EscapeDefault {
100+
pub(crate) fn empty() -> Self {
101+
let data = [Char::Null; 4];
102+
EscapeDefault(escape::EscapeIterInner::new(data, 0..0))
103+
}
104+
105+
pub(crate) fn as_str(&self) -> &str {
106+
self.0.as_str()
107+
}
108+
}
109+
99110
#[stable(feature = "rust1", since = "1.0.0")]
100111
impl Iterator for EscapeDefault {
101112
type Item = u8;

library/core/src/iter/adapters/flatten.rs

+8
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ impl<I: Iterator, U: IntoIterator, F: FnMut(I::Item) -> U> FlatMap<I, U, F> {
1818
pub(in crate::iter) fn new(iter: I, f: F) -> FlatMap<I, U, F> {
1919
FlatMap { inner: FlattenCompat::new(iter.map(f)) }
2020
}
21+
22+
pub(crate) fn into_parts(self) -> (Option<U::IntoIter>, Option<I>, Option<U::IntoIter>) {
23+
(
24+
self.inner.frontiter,
25+
self.inner.iter.into_inner().map(Map::into_inner),
26+
self.inner.backiter,
27+
)
28+
}
2129
}
2230

2331
#[stable(feature = "rust1", since = "1.0.0")]

library/core/src/iter/adapters/fuse.rs

+4
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ impl<I> Fuse<I> {
2424
pub(in crate::iter) fn new(iter: I) -> Fuse<I> {
2525
Fuse { iter: Some(iter) }
2626
}
27+
28+
pub(crate) fn into_inner(self) -> Option<I> {
29+
self.iter
30+
}
2731
}
2832

2933
#[stable(feature = "fused", since = "1.26.0")]

library/core/src/iter/adapters/map.rs

+4
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ impl<I, F> Map<I, F> {
6868
pub(in crate::iter) fn new(iter: I, f: F) -> Map<I, F> {
6969
Map { iter, f }
7070
}
71+
72+
pub(crate) fn into_inner(self) -> I {
73+
self.iter
74+
}
7175
}
7276

7377
#[stable(feature = "core_impl_debug", since = "1.9.0")]

library/core/src/slice/ascii.rs

+40-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::fmt::{self, Write};
55
use crate::iter;
66
use crate::mem;
77
use crate::ops;
8+
use core::ascii::EscapeDefault;
89

910
#[cfg(not(test))]
1011
impl [u8] {
@@ -250,7 +251,45 @@ impl<'a> iter::FusedIterator for EscapeAscii<'a> {}
250251
#[stable(feature = "inherent_ascii_escape", since = "1.60.0")]
251252
impl<'a> fmt::Display for EscapeAscii<'a> {
252253
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
253-
self.clone().try_for_each(|b| f.write_char(b as char))
254+
// disassemble iterator, including front/back parts of flatmap in case it has been partially consumed
255+
let (front, slice, back) = self.clone().inner.into_parts();
256+
let front = front.unwrap_or(EscapeDefault::empty());
257+
let mut bytes = slice.unwrap_or_default().as_slice();
258+
let back = back.unwrap_or(EscapeDefault::empty());
259+
260+
// usually empty, so the formatter won't have to do any work
261+
for byte in front {
262+
f.write_char(byte as char)?;
263+
}
264+
265+
fn needs_escape(b: u8) -> bool {
266+
b > 0x7E || b < 0x20 || b == b'\\' || b == b'\'' || b == b'"'
267+
}
268+
269+
while bytes.len() > 0 {
270+
// fast path for the printable, non-escaped subset of ascii
271+
let prefix = bytes.iter().take_while(|&&b| !needs_escape(b)).count();
272+
// SAFETY: prefix length was derived by counting bytes in the same splice, so it's in-bounds
273+
let (prefix, remainder) = unsafe { bytes.split_at_unchecked(prefix) };
274+
// SAFETY: prefix is a valid utf8 sequence, as it's a subset of ASCII
275+
let prefix = unsafe { crate::str::from_utf8_unchecked(prefix) };
276+
277+
f.write_str(prefix)?; // the fast part
278+
279+
bytes = remainder;
280+
281+
if let Some(&b) = bytes.first() {
282+
// guaranteed to be non-empty, better to write it as a str
283+
f.write_str(ascii::escape_default(b).as_str())?;
284+
bytes = &bytes[1..];
285+
}
286+
}
287+
288+
// also usually empty
289+
for byte in back {
290+
f.write_char(byte as char)?;
291+
}
292+
Ok(())
254293
}
255294
}
256295
#[stable(feature = "inherent_ascii_escape", since = "1.60.0")]

library/core/tests/ascii.rs

+10
Original file line numberDiff line numberDiff line change
@@ -479,3 +479,13 @@ fn ascii_ctype_const() {
479479
is_ascii_control => [false, false, false, false, false];
480480
}
481481
}
482+
483+
#[test]
484+
fn test_ascii_display() {
485+
assert_eq!(b"foo'bar".escape_ascii().to_string(), r#"foo\'bar"#);
486+
assert_eq!(b"\0\xff".escape_ascii().to_string(), r#"\x00\xff"#);
487+
let mut it = b"\0fastpath\xffremainder\xff".escape_ascii();
488+
let _ = it.advance_by(4);
489+
let _ = it.advance_back_by(4);
490+
assert_eq!(it.to_string(), r#"fastpath\xffremainder"#);
491+
}

0 commit comments

Comments
 (0)