Skip to content

Commit d2204ce

Browse files
committed
speed up String::push and String::insert
1 parent e1f0920 commit d2204ce

File tree

4 files changed

+110
-46
lines changed

4 files changed

+110
-46
lines changed

library/alloc/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@
105105
#![feature(box_uninit_write)]
106106
#![feature(bstr)]
107107
#![feature(bstr_internals)]
108+
#![feature(char_internals)]
108109
#![feature(clone_to_uninit)]
109110
#![feature(coerce_unsized)]
110111
#![feature(const_eval_select)]

library/alloc/src/string.rs

+47-16
Original file line numberDiff line numberDiff line change
@@ -1417,9 +1417,14 @@ impl String {
14171417
#[inline]
14181418
#[stable(feature = "rust1", since = "1.0.0")]
14191419
pub fn push(&mut self, ch: char) {
1420-
match ch.len_utf8() {
1421-
1 => self.vec.push(ch as u8),
1422-
_ => self.vec.extend_from_slice(ch.encode_utf8(&mut [0; 4]).as_bytes()),
1420+
let len = self.len();
1421+
let ch_len = ch.len_utf8();
1422+
self.reserve(ch_len);
1423+
1424+
// SAFETY: Just reserved capacity for at least the length needed to encode `ch`.
1425+
unsafe {
1426+
core::char::encode_utf8_raw_unchecked(ch as u32, self.vec.as_mut_ptr().add(self.len()));
1427+
self.vec.set_len(len + ch_len);
14231428
}
14241429
}
14251430

@@ -1716,24 +1721,31 @@ impl String {
17161721
#[rustc_confusables("set")]
17171722
pub fn insert(&mut self, idx: usize, ch: char) {
17181723
assert!(self.is_char_boundary(idx));
1719-
let mut bits = [0; 4];
1720-
let bits = ch.encode_utf8(&mut bits).as_bytes();
17211724

1725+
let len = self.len();
1726+
let ch_len = ch.len_utf8();
1727+
self.reserve(ch_len);
1728+
1729+
// SAFETY: Move the bytes starting from `idx` to their new location `ch_len`
1730+
// bytes ahead. This is safe because sufficient capacity was reserved, and `idx`
1731+
// is a char boundary.
17221732
unsafe {
1723-
self.insert_bytes(idx, bits);
1733+
ptr::copy(
1734+
self.vec.as_ptr().add(idx),
1735+
self.vec.as_mut_ptr().add(idx + ch_len),
1736+
len - idx,
1737+
);
17241738
}
1725-
}
17261739

1727-
#[cfg(not(no_global_oom_handling))]
1728-
unsafe fn insert_bytes(&mut self, idx: usize, bytes: &[u8]) {
1729-
let len = self.len();
1730-
let amt = bytes.len();
1731-
self.vec.reserve(amt);
1740+
// SAFETY: Encode the character into the vacated region if `idx != len`,
1741+
// or into the uninitialized spare capacity otherwise.
1742+
unsafe {
1743+
core::char::encode_utf8_raw_unchecked(ch as u32, self.vec.as_mut_ptr().add(idx));
1744+
}
17321745

1746+
// SAFETY: Update the length to include the newly added bytes.
17331747
unsafe {
1734-
ptr::copy(self.vec.as_ptr().add(idx), self.vec.as_mut_ptr().add(idx + amt), len - idx);
1735-
ptr::copy_nonoverlapping(bytes.as_ptr(), self.vec.as_mut_ptr().add(idx), amt);
1736-
self.vec.set_len(len + amt);
1748+
self.vec.set_len(len + ch_len);
17371749
}
17381750
}
17391751

@@ -1763,8 +1775,27 @@ impl String {
17631775
pub fn insert_str(&mut self, idx: usize, string: &str) {
17641776
assert!(self.is_char_boundary(idx));
17651777

1778+
let len = self.len();
1779+
let amt = string.len();
1780+
self.reserve(amt);
1781+
1782+
// SAFETY: Move the bytes starting from `idx` to their new location `amt` bytes
1783+
// ahead. This is safe because sufficient capacity was just reserved, and `idx`
1784+
// is a char boundary.
17661785
unsafe {
1767-
self.insert_bytes(idx, string.as_bytes());
1786+
ptr::copy(self.vec.as_ptr().add(idx), self.vec.as_mut_ptr().add(idx + amt), len - idx);
1787+
}
1788+
1789+
// SAFETY: Copy the new string slice into the vacated region if `idx != len`,
1790+
// or into the uninitialized spare capacity otherwise. The borrow checker
1791+
// ensures that the source and destination do not overlap.
1792+
unsafe {
1793+
ptr::copy_nonoverlapping(string.as_ptr(), self.vec.as_mut_ptr().add(idx), amt);
1794+
}
1795+
1796+
// SAFETY: Update the length to include the newly added bytes.
1797+
unsafe {
1798+
self.vec.set_len(len + amt);
17681799
}
17691800
}
17701801

library/core/src/char/methods.rs

+61-29
Original file line numberDiff line numberDiff line change
@@ -1795,39 +1795,71 @@ const fn len_utf16(code: u32) -> usize {
17951795
#[inline]
17961796
pub const fn encode_utf8_raw(code: u32, dst: &mut [u8]) -> &mut [u8] {
17971797
let len = len_utf8(code);
1798-
match (len, &mut *dst) {
1799-
(1, [a, ..]) => {
1800-
*a = code as u8;
1801-
}
1802-
(2, [a, b, ..]) => {
1803-
*a = (code >> 6 & 0x1F) as u8 | TAG_TWO_B;
1804-
*b = (code & 0x3F) as u8 | TAG_CONT;
1805-
}
1806-
(3, [a, b, c, ..]) => {
1807-
*a = (code >> 12 & 0x0F) as u8 | TAG_THREE_B;
1808-
*b = (code >> 6 & 0x3F) as u8 | TAG_CONT;
1809-
*c = (code & 0x3F) as u8 | TAG_CONT;
1810-
}
1811-
(4, [a, b, c, d, ..]) => {
1812-
*a = (code >> 18 & 0x07) as u8 | TAG_FOUR_B;
1813-
*b = (code >> 12 & 0x3F) as u8 | TAG_CONT;
1814-
*c = (code >> 6 & 0x3F) as u8 | TAG_CONT;
1815-
*d = (code & 0x3F) as u8 | TAG_CONT;
1816-
}
1817-
_ => {
1818-
const_panic!(
1819-
"encode_utf8: buffer does not have enough bytes to encode code point",
1820-
"encode_utf8: need {len} bytes to encode U+{code:04X} but buffer has just {dst_len}",
1821-
code: u32 = code,
1822-
len: usize = len,
1823-
dst_len: usize = dst.len(),
1824-
)
1825-
}
1826-
};
1798+
if dst.len() < len {
1799+
const_panic!(
1800+
"encode_utf8: buffer does not have enough bytes to encode code point",
1801+
"encode_utf8: need {len} bytes to encode U+{code:04X} but buffer has just {dst_len}",
1802+
code: u32 = code,
1803+
len: usize = len,
1804+
dst_len: usize = dst.len(),
1805+
);
1806+
}
1807+
1808+
// SAFETY: `dst` is checked to be at least the length needed to encode the codepoint.
1809+
unsafe { encode_utf8_raw_unchecked(code, dst.as_mut_ptr()) };
1810+
18271811
// SAFETY: `<&mut [u8]>::as_mut_ptr` is guaranteed to return a valid pointer and `len` has been tested to be within bounds.
18281812
unsafe { slice::from_raw_parts_mut(dst.as_mut_ptr(), len) }
18291813
}
18301814

1815+
/// Encodes a raw u32 value as UTF-8 to the provided destination buffer.
1816+
///
1817+
/// Unlike `char::encode_utf8`, this method also handles codepoints in the surrogate range.
1818+
/// (Creating a `char` in the surrogate range is UB.)
1819+
/// The result is valid [generalized UTF-8] but not valid UTF-8.
1820+
///
1821+
/// [generalized UTF-8]: https://simonsapin.github.io/wtf-8/#generalized-utf8
1822+
///
1823+
/// # Safety
1824+
///
1825+
/// The behavior is undefined if the buffer pointed to by `dst` is not
1826+
/// large enough to hold the encoded codepoint. A buffer of length four
1827+
/// is large enough to encode any `char`.
1828+
///
1829+
/// For a safe version of this function, see the [`encode_utf8_raw`] function.
1830+
#[unstable(feature = "char_internals", reason = "exposed only for libstd", issue = "none")]
1831+
#[doc(hidden)]
1832+
#[inline]
1833+
pub const unsafe fn encode_utf8_raw_unchecked(code: u32, dst: *mut u8) {
1834+
let len = len_utf8(code);
1835+
// SAFETY: The caller must guarantee that the buffer pointed to by `dst`
1836+
// is at least `len` bytes long.
1837+
unsafe {
1838+
match len {
1839+
1 => {
1840+
*dst = code as u8;
1841+
}
1842+
2 => {
1843+
*dst = (code >> 6 & 0x1F) as u8 | TAG_TWO_B;
1844+
*dst.add(1) = (code & 0x3F) as u8 | TAG_CONT;
1845+
}
1846+
3 => {
1847+
*dst = (code >> 12 & 0x0F) as u8 | TAG_THREE_B;
1848+
*dst.add(1) = (code >> 6 & 0x3F) as u8 | TAG_CONT;
1849+
*dst.add(2) = (code & 0x3F) as u8 | TAG_CONT;
1850+
}
1851+
4 => {
1852+
*dst = (code >> 18 & 0x07) as u8 | TAG_FOUR_B;
1853+
*dst.add(1) = (code >> 12 & 0x3F) as u8 | TAG_CONT;
1854+
*dst.add(2) = (code >> 6 & 0x3F) as u8 | TAG_CONT;
1855+
*dst.add(3) = (code & 0x3F) as u8 | TAG_CONT;
1856+
}
1857+
// SAFETY: `char` always takes between 1 and 4 bytes to encode in UTF-8.
1858+
_ => crate::hint::unreachable_unchecked(),
1859+
}
1860+
}
1861+
}
1862+
18311863
/// Encodes a raw `u32` value as UTF-16 into the provided `u16` buffer,
18321864
/// and then returns the subslice of the buffer that contains the encoded character.
18331865
///

library/core/src/char/mod.rs

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

4343
#[rustfmt::skip]
4444
use crate::ascii;

0 commit comments

Comments
 (0)