Skip to content

Commit 4f45c38

Browse files
committed
Auto merge of rust-lang#136264 - GuillaumeGomez:optimize-integers-to-string, r=Amanieu
Optimize `ToString` implementation for integers Part of rust-lang#135543. Follow-up of rust-lang#133247 and rust-lang#128204. The benchmark results are: | name| 1.87.0-nightly (3ea711f 2025-03-09) | With this PR | diff | |-|-|-|-| | bench_i16 | 32.06 ns/iter (+/- 0.12) | 17.62 ns/iter (+/- 0.03) | -45% | | bench_i32 | 31.61 ns/iter (+/- 0.04) | 15.10 ns/iter (+/- 0.06) | -52% | | bench_i64 | 31.71 ns/iter (+/- 0.07) | 15.02 ns/iter (+/- 0.20) | -52% | | bench_i8 | 13.21 ns/iter (+/- 0.14) | 14.93 ns/iter (+/- 0.16) | +13% | | bench_u16 | 31.20 ns/iter (+/- 0.06) | 16.14 ns/iter (+/- 0.11) | -48% | | bench_u32 | 33.27 ns/iter (+/- 0.05) | 16.18 ns/iter (+/- 0.10) | -51% | | bench_u64 | 31.44 ns/iter (+/- 0.06) | 16.62 ns/iter (+/- 0.21) | -47% | | bench_u8 | 10.57 ns/iter (+/- 0.30) | 13.00 ns/iter (+/- 0.43) | +22% | More information about it in [the original comment](rust-lang#136264 (comment)). r? `@workingjubilee`
2 parents 414482f + 1ef7585 commit 4f45c38

File tree

8 files changed

+73
-15
lines changed

8 files changed

+73
-15
lines changed

library/alloc/src/string.rs

+48
Original file line numberDiff line numberDiff line change
@@ -2826,7 +2826,54 @@ impl SpecToString for bool {
28262826
}
28272827
}
28282828

2829+
macro_rules! impl_to_string {
2830+
($($signed:ident, $unsigned:ident,)*) => {
2831+
$(
2832+
#[cfg(not(no_global_oom_handling))]
2833+
#[cfg(not(feature = "optimize_for_size"))]
2834+
impl SpecToString for $signed {
2835+
#[inline]
2836+
fn spec_to_string(&self) -> String {
2837+
const SIZE: usize = $signed::MAX.ilog(10) as usize + 1;
2838+
let mut buf = [core::mem::MaybeUninit::<u8>::uninit(); SIZE];
2839+
// Only difference between signed and unsigned are these 8 lines.
2840+
let mut out;
2841+
if *self < 0 {
2842+
out = String::with_capacity(SIZE + 1);
2843+
out.push('-');
2844+
} else {
2845+
out = String::with_capacity(SIZE);
2846+
}
2847+
2848+
out.push_str(self.unsigned_abs()._fmt(&mut buf));
2849+
out
2850+
}
2851+
}
2852+
#[cfg(not(no_global_oom_handling))]
2853+
#[cfg(not(feature = "optimize_for_size"))]
2854+
impl SpecToString for $unsigned {
2855+
#[inline]
2856+
fn spec_to_string(&self) -> String {
2857+
const SIZE: usize = $unsigned::MAX.ilog(10) as usize + 1;
2858+
let mut buf = [core::mem::MaybeUninit::<u8>::uninit(); SIZE];
2859+
2860+
self._fmt(&mut buf).to_string()
2861+
}
2862+
}
2863+
)*
2864+
}
2865+
}
2866+
2867+
impl_to_string! {
2868+
i8, u8,
2869+
i16, u16,
2870+
i32, u32,
2871+
i64, u64,
2872+
isize, usize,
2873+
}
2874+
28292875
#[cfg(not(no_global_oom_handling))]
2876+
#[cfg(feature = "optimize_for_size")]
28302877
impl SpecToString for u8 {
28312878
#[inline]
28322879
fn spec_to_string(&self) -> String {
@@ -2846,6 +2893,7 @@ impl SpecToString for u8 {
28462893
}
28472894

28482895
#[cfg(not(no_global_oom_handling))]
2896+
#[cfg(feature = "optimize_for_size")]
28492897
impl SpecToString for i8 {
28502898
#[inline]
28512899
fn spec_to_string(&self) -> String {

library/core/src/fmt/num.rs

+19-9
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,11 @@ macro_rules! impl_Display {
208208
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
209209
#[cfg(not(feature = "optimize_for_size"))]
210210
{
211-
self._fmt(true, f)
211+
const MAX_DEC_N: usize = $unsigned::MAX.ilog(10) as usize + 1;
212+
// Buffer decimals for $unsigned with right alignment.
213+
let mut buf = [MaybeUninit::<u8>::uninit(); MAX_DEC_N];
214+
215+
f.pad_integral(true, "", self._fmt(&mut buf))
212216
}
213217
#[cfg(feature = "optimize_for_size")]
214218
{
@@ -222,7 +226,11 @@ macro_rules! impl_Display {
222226
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
223227
#[cfg(not(feature = "optimize_for_size"))]
224228
{
225-
return self.unsigned_abs()._fmt(*self >= 0, f);
229+
const MAX_DEC_N: usize = $unsigned::MAX.ilog(10) as usize + 1;
230+
// Buffer decimals for $unsigned with right alignment.
231+
let mut buf = [MaybeUninit::<u8>::uninit(); MAX_DEC_N];
232+
233+
f.pad_integral(*self >= 0, "", self.unsigned_abs()._fmt(&mut buf))
226234
}
227235
#[cfg(feature = "optimize_for_size")]
228236
{
@@ -233,10 +241,13 @@ macro_rules! impl_Display {
233241

234242
#[cfg(not(feature = "optimize_for_size"))]
235243
impl $unsigned {
236-
fn _fmt(self, is_nonnegative: bool, f: &mut fmt::Formatter<'_>) -> fmt::Result {
237-
const MAX_DEC_N: usize = $unsigned::MAX.ilog(10) as usize + 1;
238-
// Buffer decimals for $unsigned with right alignment.
239-
let mut buf = [MaybeUninit::<u8>::uninit(); MAX_DEC_N];
244+
#[doc(hidden)]
245+
#[unstable(
246+
feature = "fmt_internals",
247+
reason = "specialized method meant to only be used by `SpecToString` implementation",
248+
issue = "none"
249+
)]
250+
pub fn _fmt<'a>(self, buf: &'a mut [MaybeUninit::<u8>]) -> &'a str {
240251
// Count the number of bytes in buf that are not initialized.
241252
let mut offset = buf.len();
242253
// Consume the least-significant decimals from a working copy.
@@ -301,13 +312,12 @@ macro_rules! impl_Display {
301312
// SAFETY: All buf content since offset is set.
302313
let written = unsafe { buf.get_unchecked(offset..) };
303314
// SAFETY: Writes use ASCII from the lookup table exclusively.
304-
let as_str = unsafe {
315+
unsafe {
305316
str::from_utf8_unchecked(slice::from_raw_parts(
306317
MaybeUninit::slice_as_ptr(written),
307318
written.len(),
308319
))
309-
};
310-
f.pad_integral(is_nonnegative, "", as_str)
320+
}
311321
}
312322
})*
313323

tests/ui/codegen/equal-pointers-unequal/as-cast/inline2.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ fn main() {
2323
let v = 0;
2424
&v as *const _ as usize
2525
};
26-
assert_eq!(a.to_string(), b.to_string());
26+
assert_eq!(format!("{a}"), format!("{b}"));
2727
assert_eq!(format!("{}", a == b), "true");
2828
assert_eq!(format!("{}", cmp_in(a, b)), "true");
2929
assert_eq!(format!("{}", cmp(a, b)), "true");

tests/ui/codegen/equal-pointers-unequal/as-cast/zero.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ fn main() {
2121
// It's not zero, which means `a` and `b` are not equal.
2222
assert_ne!(i, 0);
2323
// But it looks like zero...
24-
assert_eq!(i.to_string(), "0");
24+
assert_eq!(format!("{i}"), "0");
2525
// ...and now it *is* zero?
2626
assert_eq!(i, 0);
2727
// So `a` and `b` are equal after all?

tests/ui/codegen/equal-pointers-unequal/exposed-provenance/inline2.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ fn main() {
2525
let v = 0;
2626
ptr::from_ref(&v).expose_provenance()
2727
};
28-
assert_eq!(a.to_string(), b.to_string());
28+
assert_eq!(format!("{a}"), format!("{b}"));
2929
assert_eq!(format!("{}", a == b), "true");
3030
assert_eq!(format!("{}", cmp_in(a, b)), "true");
3131
assert_eq!(format!("{}", cmp(a, b)), "true");

tests/ui/codegen/equal-pointers-unequal/exposed-provenance/zero.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ fn main() {
2323
// It's not zero, which means `a` and `b` are not equal.
2424
assert_ne!(i, 0);
2525
// But it looks like zero...
26-
assert_eq!(i.to_string(), "0");
26+
assert_eq!(format!("{i}"), "0");
2727
// ...and now it *is* zero?
2828
assert_eq!(i, 0);
2929
// So `a` and `b` are equal after all?

tests/ui/codegen/equal-pointers-unequal/strict-provenance/inline2.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ fn main() {
2525
let v = 0;
2626
ptr::from_ref(&v).addr()
2727
};
28-
assert_eq!(a.to_string(), b.to_string());
28+
assert_eq!(format!("{a}"), format!("{b}"));
2929
assert_eq!(format!("{}", a == b), "true");
3030
assert_eq!(format!("{}", cmp_in(a, b)), "true");
3131
assert_eq!(format!("{}", cmp(a, b)), "true");

tests/ui/codegen/equal-pointers-unequal/strict-provenance/zero.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ fn main() {
2323
// It's not zero, which means `a` and `b` are not equal.
2424
assert_ne!(i, 0);
2525
// But it looks like zero...
26-
assert_eq!(i.to_string(), "0");
26+
assert_eq!(format!("{i}"), "0");
2727
// ...and now it *is* zero?
2828
assert_eq!(i, 0);
2929
// So `a` and `b` are equal after all?

0 commit comments

Comments
 (0)