Skip to content

Commit 522da80

Browse files
Auto merge of #149705 - paolobarbolini:string-extend-chunked, r=<try>
Optimize `Extend for String`
2 parents 1c5a0cf + b6110e5 commit 522da80

File tree

6 files changed

+122
-29
lines changed

6 files changed

+122
-29
lines changed

compiler/rustc_ast/src/ast.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -181,8 +181,7 @@ pub fn join_path_syms(path: impl IntoIterator<Item = impl Borrow<Symbol>>) -> St
181181
for sym in iter {
182182
let sym = *sym.borrow();
183183
debug_assert_ne!(sym, kw::PathRoot);
184-
s.push_str("::");
185-
s.push_str(sym.as_str());
184+
s.extend(["::", sym.as_str()]);
186185
}
187186
s
188187
}
@@ -201,8 +200,7 @@ pub fn join_path_idents(path: impl IntoIterator<Item = impl Borrow<Ident>>) -> S
201200
for ident in iter {
202201
let ident = *ident.borrow();
203202
debug_assert_ne!(ident.name, kw::PathRoot);
204-
s.push_str("::");
205-
s.push_str(&ident.to_string());
203+
s.extend(["::", &ident.to_string()]);
206204
}
207205
s
208206
}

compiler/rustc_builtin_macros/src/assert/context.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -334,9 +334,7 @@ impl<'cx, 'a> Context<'cx, 'a> {
334334
if self.paths.contains(&path_ident) {
335335
return;
336336
} else {
337-
self.fmt_string.push_str(" ");
338-
self.fmt_string.push_str(path_ident.as_str());
339-
self.fmt_string.push_str(" = {:?}\n");
337+
self.fmt_string.extend([" ", path_ident.as_str(), " = {:?}\n"]);
340338
let _ = self.paths.insert(path_ident);
341339
}
342340
let curr_capture_idx = self.capture_decls.len();

library/alloc/src/borrow.rs

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ use core::ops::{Deref, DerefPure};
1313
use Cow::*;
1414

1515
use crate::fmt;
16-
#[cfg(not(no_global_oom_handling))]
17-
use crate::string::String;
1816

1917
// FIXME(inference): const bounds removed due to inference regressions found by crater;
2018
// see https://github.com/rust-lang/rust/issues/147964
@@ -496,12 +494,14 @@ impl<'a> AddAssign<&'a str> for Cow<'a, str> {
496494
if self.is_empty() {
497495
*self = Cow::Borrowed(rhs)
498496
} else if !rhs.is_empty() {
499-
if let Cow::Borrowed(lhs) = *self {
500-
let mut s = String::with_capacity(lhs.len() + rhs.len());
501-
s.push_str(lhs);
502-
*self = Cow::Owned(s);
497+
match self {
498+
Self::Borrowed(lhs) => {
499+
*self = Cow::Owned([lhs, rhs].into_iter().collect());
500+
}
501+
Self::Owned(lhs) => {
502+
lhs.push_str(&rhs);
503+
}
503504
}
504-
self.to_mut().push_str(rhs);
505505
}
506506
}
507507
}
@@ -513,12 +513,14 @@ impl<'a> AddAssign<Cow<'a, str>> for Cow<'a, str> {
513513
if self.is_empty() {
514514
*self = rhs
515515
} else if !rhs.is_empty() {
516-
if let Cow::Borrowed(lhs) = *self {
517-
let mut s = String::with_capacity(lhs.len() + rhs.len());
518-
s.push_str(lhs);
519-
*self = Cow::Owned(s);
516+
match self {
517+
Self::Borrowed(lhs) => {
518+
*self = Cow::Owned([&*lhs, &*rhs].into_iter().collect());
519+
}
520+
Self::Owned(lhs) => {
521+
lhs.push_str(&rhs);
522+
}
520523
}
521-
self.to_mut().push_str(&rhs);
522524
}
523525
}
524526
}

library/alloc/src/string.rs

Lines changed: 103 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ use core::ops::AddAssign;
5454
use core::ops::Bound::{Excluded, Included, Unbounded};
5555
use core::ops::{self, Range, RangeBounds};
5656
use core::str::pattern::{Pattern, Utf8Pattern};
57-
use core::{fmt, hash, ptr, slice};
57+
use core::{array, fmt, hash, ptr, slice};
5858

5959
#[cfg(not(no_global_oom_handling))]
6060
use crate::alloc::Allocator;
@@ -2203,6 +2203,35 @@ impl String {
22032203
let slice = self.vec.leak();
22042204
unsafe { from_utf8_unchecked_mut(slice) }
22052205
}
2206+
2207+
/// SAFETY: `impl AsRef<str> for S` must be stable
2208+
#[cfg(not(no_global_oom_handling))]
2209+
unsafe fn extend_many<S: AsRef<str>>(&mut self, vals: &[S]) {
2210+
let additional = vals.iter().fold(0usize, |a, s| a.saturating_add(s.as_ref().len()));
2211+
self.reserve(additional);
2212+
2213+
let mut spare = self.vec.spare_capacity_mut().as_mut_ptr().cast_init();
2214+
for val in vals {
2215+
let val = val.as_ref();
2216+
// SAFETY:
2217+
// - `val` is a valid &str, so `val.as_ptr()` is valid
2218+
// for `val.len()` bytes and properly initialized.
2219+
// - `spare` points to valid spare capacity in the Vec
2220+
// with enough space for `val.len()` bytes.
2221+
// This is guaranteed because the caller ensures
2222+
// that `.as_ref()` is stable, and the saturating
2223+
// addition stops undercounting by overflow.
2224+
// - Both pointers are byte-aligned and the regions cannot overlap.
2225+
unsafe {
2226+
ptr::copy_nonoverlapping(val.as_ptr(), spare, val.len());
2227+
spare = spare.add(val.len());
2228+
}
2229+
}
2230+
2231+
let new_len = self.vec.len() + additional;
2232+
// SAFETY: the elements have just been initialized
2233+
unsafe { self.vec.set_len(new_len) }
2234+
}
22062235
}
22072236

22082237
impl FromUtf8Error {
@@ -2502,7 +2531,8 @@ impl<'a> Extend<&'a char> for String {
25022531
#[stable(feature = "rust1", since = "1.0.0")]
25032532
impl<'a> Extend<&'a str> for String {
25042533
fn extend<I: IntoIterator<Item = &'a str>>(&mut self, iter: I) {
2505-
iter.into_iter().for_each(move |s| self.push_str(s));
2534+
// SAFETY: `impl AsRef<str> for &str` is stable
2535+
unsafe { self.extend_many_chunked(iter.into_iter()) }
25062536
}
25072537

25082538
#[inline]
@@ -2515,15 +2545,17 @@ impl<'a> Extend<&'a str> for String {
25152545
#[stable(feature = "box_str2", since = "1.45.0")]
25162546
impl<A: Allocator> Extend<Box<str, A>> for String {
25172547
fn extend<I: IntoIterator<Item = Box<str, A>>>(&mut self, iter: I) {
2518-
iter.into_iter().for_each(move |s| self.push_str(&s));
2548+
// SAFETY: `impl AsRef<str> for Box<str, A>` is stable
2549+
unsafe { self.extend_many_chunked(iter.into_iter()) }
25192550
}
25202551
}
25212552

25222553
#[cfg(not(no_global_oom_handling))]
25232554
#[stable(feature = "extend_string", since = "1.4.0")]
25242555
impl Extend<String> for String {
25252556
fn extend<I: IntoIterator<Item = String>>(&mut self, iter: I) {
2526-
iter.into_iter().for_each(move |s| self.push_str(&s));
2557+
// SAFETY: `impl AsRef<str> for String` is stable
2558+
unsafe { self.extend_many_chunked(iter.into_iter()) }
25272559
}
25282560

25292561
#[inline]
@@ -2536,7 +2568,8 @@ impl Extend<String> for String {
25362568
#[stable(feature = "herd_cows", since = "1.19.0")]
25372569
impl<'a> Extend<Cow<'a, str>> for String {
25382570
fn extend<I: IntoIterator<Item = Cow<'a, str>>>(&mut self, iter: I) {
2539-
iter.into_iter().for_each(move |s| self.push_str(&s));
2571+
// SAFETY: `impl AsRef<str> for Cow<'a, str>` is stable
2572+
unsafe { self.extend_many_chunked(iter.into_iter()) }
25402573
}
25412574

25422575
#[inline]
@@ -2573,6 +2606,71 @@ impl<'a> Extend<&'a core::ascii::Char> for String {
25732606
}
25742607
}
25752608

2609+
#[cfg(not(no_global_oom_handling))]
2610+
trait ExtendManySpec<S, I> {
2611+
/// SAFETY: `impl AsRef<str> for S` must be stable
2612+
unsafe fn extend_many_chunked(&mut self, iter: I);
2613+
}
2614+
2615+
#[cfg(not(no_global_oom_handling))]
2616+
impl<S, I> ExtendManySpec<S, I> for String
2617+
where
2618+
S: AsRef<str>,
2619+
I: Iterator<Item = S>,
2620+
{
2621+
default unsafe fn extend_many_chunked(&mut self, mut iter: I) {
2622+
let mut repeat = true;
2623+
while repeat {
2624+
let chunk = match iter.next_chunk::<8>() {
2625+
Ok(chunk) => chunk.into_iter(),
2626+
Err(partial_chunk) => {
2627+
repeat = false;
2628+
partial_chunk
2629+
}
2630+
};
2631+
2632+
// SAFETY: the caller ensures that `impl AsRef<str> for S` is stable
2633+
unsafe { self.extend_many(chunk.as_slice()) }
2634+
}
2635+
}
2636+
}
2637+
2638+
// TODO: specialization thinks that this is a conflicting implementation
2639+
/*
2640+
#[cfg(not(no_global_oom_handling))]
2641+
impl<'a, S> ExtendManySpec<S, slice::Iter<'a, S>> for String
2642+
where
2643+
S: AsRef<str>,
2644+
{
2645+
unsafe fn extend_many_chunked(&mut self, iter: slice::Iter<'a, S>) {
2646+
// SAFETY: the caller ensures that `impl AsRef<str> for S` is stable
2647+
unsafe { self.extend_many(iter.as_slice()) }
2648+
}
2649+
}
2650+
*/
2651+
2652+
#[cfg(not(no_global_oom_handling))]
2653+
impl<S, const N: usize> ExtendManySpec<S, array::IntoIter<S, N>> for String
2654+
where
2655+
S: AsRef<str>,
2656+
{
2657+
unsafe fn extend_many_chunked(&mut self, iter: array::IntoIter<S, N>) {
2658+
// SAFETY: the caller ensures that `impl AsRef<str> for S` is stable
2659+
unsafe { self.extend_many(iter.as_slice()) }
2660+
}
2661+
}
2662+
2663+
#[cfg(not(no_global_oom_handling))]
2664+
impl<S> ExtendManySpec<S, vec::IntoIter<S>> for String
2665+
where
2666+
S: AsRef<str>,
2667+
{
2668+
unsafe fn extend_many_chunked(&mut self, iter: vec::IntoIter<S>) {
2669+
// SAFETY: the caller ensures that `impl AsRef<str> for S` is stable
2670+
unsafe { self.extend_many(iter.as_slice()) }
2671+
}
2672+
}
2673+
25762674
/// A convenience impl that delegates to the impl for `&str`.
25772675
///
25782676
/// # Examples

src/librustdoc/html/markdown.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1645,9 +1645,7 @@ pub(crate) fn plain_text_from_events<'a>(
16451645
match &event {
16461646
Event::Text(text) => s.push_str(text),
16471647
Event::Code(code) => {
1648-
s.push('`');
1649-
s.push_str(code);
1650-
s.push('`');
1648+
s.extend(["`", code, "`"]);
16511649
}
16521650
Event::HardBreak | Event::SoftBreak => s.push(' '),
16531651
Event::Start(Tag::CodeBlock(..)) => break,

src/librustdoc/html/render/print_item.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2257,8 +2257,7 @@ pub(crate) fn compare_names(left: &str, right: &str) -> Ordering {
22572257

22582258
pub(super) fn full_path(cx: &Context<'_>, item: &clean::Item) -> String {
22592259
let mut s = join_path_syms(&cx.current);
2260-
s.push_str("::");
2261-
s.push_str(item.name.unwrap().as_str());
2260+
s.extend(["::", item.name.unwrap().as_str()]);
22622261
s
22632262
}
22642263

0 commit comments

Comments
 (0)