Skip to content

Commit 2e719f2

Browse files
committed
speed up String::push and String::insert
1 parent 20aa2d8 commit 2e719f2

File tree

4 files changed

+96
-40
lines changed

4 files changed

+96
-40
lines changed

library/alloc/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@
103103
#![feature(assert_matches)]
104104
#![feature(async_fn_traits)]
105105
#![feature(async_iterator)]
106+
#![feature(char_internals)]
106107
#![feature(coerce_unsized)]
107108
#![feature(const_align_of_val)]
108109
#![feature(const_box)]

library/alloc/src/string.rs

+37-16
Original file line numberDiff line numberDiff line change
@@ -1354,9 +1354,14 @@ impl String {
13541354
#[inline]
13551355
#[stable(feature = "rust1", since = "1.0.0")]
13561356
pub fn push(&mut self, ch: char) {
1357-
match ch.len_utf8() {
1358-
1 => self.vec.push(ch as u8),
1359-
_ => self.vec.extend_from_slice(ch.encode_utf8(&mut [0; 4]).as_bytes()),
1357+
let len = self.len();
1358+
let ch_len = ch.len_utf8();
1359+
self.reserve(ch_len);
1360+
1361+
// SAFETY: just reserved capacity for at least the length needed to encode `ch`
1362+
unsafe {
1363+
core::char::encode_utf8_raw_unchecked(ch as u32, self.vec.spare_capacity_mut());
1364+
self.vec.set_len(len + ch_len);
13601365
}
13611366
}
13621367

@@ -1655,24 +1660,34 @@ impl String {
16551660
#[rustc_confusables("set")]
16561661
pub fn insert(&mut self, idx: usize, ch: char) {
16571662
assert!(self.is_char_boundary(idx));
1658-
let mut bits = [0; 4];
1659-
let bits = ch.encode_utf8(&mut bits).as_bytes();
16601663

1664+
let len = self.len();
1665+
let ch_len = ch.len_utf8();
1666+
self.reserve(ch_len);
1667+
1668+
// SAFETY: shift data `ch_len` bytes to the right,
1669+
// capacity was just reserved for at least that many bytes
16611670
unsafe {
1662-
self.insert_bytes(idx, bits);
1671+
ptr::copy(
1672+
self.vec.as_ptr().add(idx),
1673+
self.vec.as_mut_ptr().add(idx + ch_len),
1674+
len - idx,
1675+
);
16631676
}
1664-
}
16651677

1666-
#[cfg(not(no_global_oom_handling))]
1667-
unsafe fn insert_bytes(&mut self, idx: usize, bytes: &[u8]) {
1668-
let len = self.len();
1669-
let amt = bytes.len();
1670-
self.vec.reserve(amt);
1678+
// SAFETY: encode the character into the space left after the shift if `idx != len`,
1679+
// or into the uninitialized spare capacity otherwise
1680+
unsafe {
1681+
let dst = slice::from_raw_parts_mut(
1682+
self.vec.as_mut_ptr().add(idx) as *mut core::mem::MaybeUninit<u8>,
1683+
ch_len,
1684+
);
1685+
core::char::encode_utf8_raw_unchecked(ch as u32, dst);
1686+
}
16711687

1688+
// SAFETY: `ch_len` initialized bytes have been added
16721689
unsafe {
1673-
ptr::copy(self.vec.as_ptr().add(idx), self.vec.as_mut_ptr().add(idx + amt), len - idx);
1674-
ptr::copy_nonoverlapping(bytes.as_ptr(), self.vec.as_mut_ptr().add(idx), amt);
1675-
self.vec.set_len(len + amt);
1690+
self.vec.set_len(len + ch_len);
16761691
}
16771692
}
16781693

@@ -1701,8 +1716,14 @@ impl String {
17011716
pub fn insert_str(&mut self, idx: usize, string: &str) {
17021717
assert!(self.is_char_boundary(idx));
17031718

1719+
let len = self.len();
1720+
let amt = string.len();
1721+
self.reserve(amt);
1722+
17041723
unsafe {
1705-
self.insert_bytes(idx, string.as_bytes());
1724+
ptr::copy(self.vec.as_ptr().add(idx), self.vec.as_mut_ptr().add(idx + amt), len - idx);
1725+
ptr::copy_nonoverlapping(string.as_ptr(), self.vec.as_mut_ptr().add(idx), amt);
1726+
self.vec.set_len(len + amt);
17061727
}
17071728
}
17081729

library/core/src/char/methods.rs

+57-23
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! impl char {}
22
3+
use crate::mem::MaybeUninit;
34
use crate::slice;
45
use crate::str::from_utf8_unchecked_mut;
56
use crate::unicode::printable::is_printable;
@@ -1768,33 +1769,66 @@ const fn len_utf8(code: u32) -> usize {
17681769
#[inline]
17691770
pub fn encode_utf8_raw(code: u32, dst: &mut [u8]) -> &mut [u8] {
17701771
let len = len_utf8(code);
1771-
match (len, &mut dst[..]) {
1772-
(1, [a, ..]) => {
1773-
*a = code as u8;
1774-
}
1775-
(2, [a, b, ..]) => {
1776-
*a = (code >> 6 & 0x1F) as u8 | TAG_TWO_B;
1777-
*b = (code & 0x3F) as u8 | TAG_CONT;
1778-
}
1779-
(3, [a, b, c, ..]) => {
1780-
*a = (code >> 12 & 0x0F) as u8 | TAG_THREE_B;
1781-
*b = (code >> 6 & 0x3F) as u8 | TAG_CONT;
1782-
*c = (code & 0x3F) as u8 | TAG_CONT;
1783-
}
1784-
(4, [a, b, c, d, ..]) => {
1785-
*a = (code >> 18 & 0x07) as u8 | TAG_FOUR_B;
1786-
*b = (code >> 12 & 0x3F) as u8 | TAG_CONT;
1787-
*c = (code >> 6 & 0x3F) as u8 | TAG_CONT;
1788-
*d = (code & 0x3F) as u8 | TAG_CONT;
1789-
}
1790-
_ => panic!(
1772+
if dst.len() < len {
1773+
panic!(
17911774
"encode_utf8: need {} bytes to encode U+{:X}, but the buffer has {}",
17921775
len,
17931776
code,
17941777
dst.len(),
1795-
),
1796-
};
1797-
&mut dst[..len]
1778+
);
1779+
}
1780+
// SAFETY: it's safe to pretend that the bytes in the slice may be uninitialized
1781+
let dst = unsafe { &mut *(dst as *mut [u8] as *mut [MaybeUninit<u8>]) };
1782+
// SAFETY: `dst` has been checked to be long enough to hold the encoded codepoint
1783+
unsafe { encode_utf8_raw_unchecked(code, dst) }
1784+
}
1785+
1786+
/// Encodes a raw u32 value as UTF-8 into the provided possibly uninitialized byte buffer,
1787+
/// and then returns the subslice of the buffer that contains the encoded character.
1788+
///
1789+
/// Unlike `char::encode_utf8`, this method also handles codepoints in the surrogate range.
1790+
/// (Creating a `char` in the surrogate range is UB.)
1791+
/// The result is valid [generalized UTF-8] but not valid UTF-8.
1792+
///
1793+
/// [generalized UTF-8]: https://simonsapin.github.io/wtf-8/#generalized-utf8
1794+
///
1795+
/// # Safety
1796+
///
1797+
/// The behavior is undefined if the buffer is not large enough to hold the encoded codepoint.
1798+
/// A buffer of length four is large enough to encode any `char`.
1799+
///
1800+
/// For a safe version of this function, see the [`encode_utf8_raw`] function.
1801+
#[unstable(feature = "char_internals", reason = "exposed only for libstd", issue = "none")]
1802+
#[doc(hidden)]
1803+
#[inline]
1804+
pub unsafe fn encode_utf8_raw_unchecked(code: u32, dst: &mut [MaybeUninit<u8>]) -> &mut [u8] {
1805+
let len = len_utf8(code);
1806+
// SAFETY: the caller must guarantee that `dst` is at least `len` bytes long
1807+
unsafe {
1808+
match len {
1809+
1 => {
1810+
dst.get_unchecked_mut(0).write(code as u8);
1811+
}
1812+
2 => {
1813+
dst.get_unchecked_mut(0).write((code >> 6 & 0x1F) as u8 | TAG_TWO_B);
1814+
dst.get_unchecked_mut(1).write((code & 0x3F) as u8 | TAG_CONT);
1815+
}
1816+
3 => {
1817+
dst.get_unchecked_mut(0).write((code >> 12 & 0x0F) as u8 | TAG_THREE_B);
1818+
dst.get_unchecked_mut(1).write((code >> 6 & 0x3F) as u8 | TAG_CONT);
1819+
dst.get_unchecked_mut(2).write((code & 0x3F) as u8 | TAG_CONT);
1820+
}
1821+
4 => {
1822+
dst.get_unchecked_mut(0).write((code >> 18 & 0x07) as u8 | TAG_FOUR_B);
1823+
dst.get_unchecked_mut(1).write((code >> 12 & 0x3F) as u8 | TAG_CONT);
1824+
dst.get_unchecked_mut(2).write((code >> 6 & 0x3F) as u8 | TAG_CONT);
1825+
dst.get_unchecked_mut(3).write((code & 0x3F) as u8 | TAG_CONT);
1826+
}
1827+
_ => unreachable!(),
1828+
}
1829+
}
1830+
// SAFETY: data has been written to the first `len` bytes
1831+
unsafe { &mut *(dst.get_unchecked_mut(..len) as *mut [MaybeUninit<u8>] as *mut [u8]) }
17981832
}
17991833

18001834
/// Encodes a raw u32 value as UTF-16 into the provided `u16` buffer,

library/core/src/char/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ pub use self::decode::{DecodeUtf16, DecodeUtf16Error};
3636
#[unstable(feature = "char_internals", reason = "exposed only for libstd", issue = "none")]
3737
pub use self::methods::encode_utf16_raw;
3838
#[unstable(feature = "char_internals", reason = "exposed only for libstd", issue = "none")]
39-
pub use self::methods::encode_utf8_raw;
39+
pub use self::methods::{encode_utf8_raw, encode_utf8_raw_unchecked};
4040

4141
use crate::ascii;
4242
use crate::error::Error;

0 commit comments

Comments
 (0)