Auto merge of #136264 - GuillaumeGomez:optimize-integers-to-string, r=Amanieu
Optimize `ToString` implementation for integers
Part of https://github.com/rust-lang/rust/issues/135543.
Follow-up of https://github.com/rust-lang/rust/pull/133247 and https://github.com/rust-lang/rust/pull/128204.
The benchmark results are:
| name| 1.87.0-nightly (3ea711f17 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](https://github.com/rust-lang/rust/pull/136264#discussion_r1987542954).
r? `@workingjubilee`
This commit is contained in:
commit
d97326eabf
8 changed files with 73 additions and 15 deletions
|
|
@ -2826,7 +2826,54 @@ impl SpecToString for bool {
|
|||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_to_string {
|
||||
($($signed:ident, $unsigned:ident,)*) => {
|
||||
$(
|
||||
#[cfg(not(no_global_oom_handling))]
|
||||
#[cfg(not(feature = "optimize_for_size"))]
|
||||
impl SpecToString for $signed {
|
||||
#[inline]
|
||||
fn spec_to_string(&self) -> String {
|
||||
const SIZE: usize = $signed::MAX.ilog(10) as usize + 1;
|
||||
let mut buf = [core::mem::MaybeUninit::<u8>::uninit(); SIZE];
|
||||
// Only difference between signed and unsigned are these 8 lines.
|
||||
let mut out;
|
||||
if *self < 0 {
|
||||
out = String::with_capacity(SIZE + 1);
|
||||
out.push('-');
|
||||
} else {
|
||||
out = String::with_capacity(SIZE);
|
||||
}
|
||||
|
||||
out.push_str(self.unsigned_abs()._fmt(&mut buf));
|
||||
out
|
||||
}
|
||||
}
|
||||
#[cfg(not(no_global_oom_handling))]
|
||||
#[cfg(not(feature = "optimize_for_size"))]
|
||||
impl SpecToString for $unsigned {
|
||||
#[inline]
|
||||
fn spec_to_string(&self) -> String {
|
||||
const SIZE: usize = $unsigned::MAX.ilog(10) as usize + 1;
|
||||
let mut buf = [core::mem::MaybeUninit::<u8>::uninit(); SIZE];
|
||||
|
||||
self._fmt(&mut buf).to_string()
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
impl_to_string! {
|
||||
i8, u8,
|
||||
i16, u16,
|
||||
i32, u32,
|
||||
i64, u64,
|
||||
isize, usize,
|
||||
}
|
||||
|
||||
#[cfg(not(no_global_oom_handling))]
|
||||
#[cfg(feature = "optimize_for_size")]
|
||||
impl SpecToString for u8 {
|
||||
#[inline]
|
||||
fn spec_to_string(&self) -> String {
|
||||
|
|
@ -2846,6 +2893,7 @@ impl SpecToString for u8 {
|
|||
}
|
||||
|
||||
#[cfg(not(no_global_oom_handling))]
|
||||
#[cfg(feature = "optimize_for_size")]
|
||||
impl SpecToString for i8 {
|
||||
#[inline]
|
||||
fn spec_to_string(&self) -> String {
|
||||
|
|
|
|||
|
|
@ -208,7 +208,11 @@ macro_rules! impl_Display {
|
|||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
#[cfg(not(feature = "optimize_for_size"))]
|
||||
{
|
||||
self._fmt(true, f)
|
||||
const MAX_DEC_N: usize = $unsigned::MAX.ilog(10) as usize + 1;
|
||||
// Buffer decimals for $unsigned with right alignment.
|
||||
let mut buf = [MaybeUninit::<u8>::uninit(); MAX_DEC_N];
|
||||
|
||||
f.pad_integral(true, "", self._fmt(&mut buf))
|
||||
}
|
||||
#[cfg(feature = "optimize_for_size")]
|
||||
{
|
||||
|
|
@ -222,7 +226,11 @@ macro_rules! impl_Display {
|
|||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
#[cfg(not(feature = "optimize_for_size"))]
|
||||
{
|
||||
return self.unsigned_abs()._fmt(*self >= 0, f);
|
||||
const MAX_DEC_N: usize = $unsigned::MAX.ilog(10) as usize + 1;
|
||||
// Buffer decimals for $unsigned with right alignment.
|
||||
let mut buf = [MaybeUninit::<u8>::uninit(); MAX_DEC_N];
|
||||
|
||||
f.pad_integral(*self >= 0, "", self.unsigned_abs()._fmt(&mut buf))
|
||||
}
|
||||
#[cfg(feature = "optimize_for_size")]
|
||||
{
|
||||
|
|
@ -233,10 +241,13 @@ macro_rules! impl_Display {
|
|||
|
||||
#[cfg(not(feature = "optimize_for_size"))]
|
||||
impl $unsigned {
|
||||
fn _fmt(self, is_nonnegative: bool, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
const MAX_DEC_N: usize = $unsigned::MAX.ilog(10) as usize + 1;
|
||||
// Buffer decimals for $unsigned with right alignment.
|
||||
let mut buf = [MaybeUninit::<u8>::uninit(); MAX_DEC_N];
|
||||
#[doc(hidden)]
|
||||
#[unstable(
|
||||
feature = "fmt_internals",
|
||||
reason = "specialized method meant to only be used by `SpecToString` implementation",
|
||||
issue = "none"
|
||||
)]
|
||||
pub fn _fmt<'a>(self, buf: &'a mut [MaybeUninit::<u8>]) -> &'a str {
|
||||
// Count the number of bytes in buf that are not initialized.
|
||||
let mut offset = buf.len();
|
||||
// Consume the least-significant decimals from a working copy.
|
||||
|
|
@ -301,13 +312,12 @@ macro_rules! impl_Display {
|
|||
// SAFETY: All buf content since offset is set.
|
||||
let written = unsafe { buf.get_unchecked(offset..) };
|
||||
// SAFETY: Writes use ASCII from the lookup table exclusively.
|
||||
let as_str = unsafe {
|
||||
unsafe {
|
||||
str::from_utf8_unchecked(slice::from_raw_parts(
|
||||
MaybeUninit::slice_as_ptr(written),
|
||||
written.len(),
|
||||
))
|
||||
};
|
||||
f.pad_integral(is_nonnegative, "", as_str)
|
||||
}
|
||||
}
|
||||
})*
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ fn main() {
|
|||
let v = 0;
|
||||
&v as *const _ as usize
|
||||
};
|
||||
assert_eq!(a.to_string(), b.to_string());
|
||||
assert_eq!(format!("{a}"), format!("{b}"));
|
||||
assert_eq!(format!("{}", a == b), "true");
|
||||
assert_eq!(format!("{}", cmp_in(a, b)), "true");
|
||||
assert_eq!(format!("{}", cmp(a, b)), "true");
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ fn main() {
|
|||
// It's not zero, which means `a` and `b` are not equal.
|
||||
assert_ne!(i, 0);
|
||||
// But it looks like zero...
|
||||
assert_eq!(i.to_string(), "0");
|
||||
assert_eq!(format!("{i}"), "0");
|
||||
// ...and now it *is* zero?
|
||||
assert_eq!(i, 0);
|
||||
// So `a` and `b` are equal after all?
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ fn main() {
|
|||
let v = 0;
|
||||
ptr::from_ref(&v).expose_provenance()
|
||||
};
|
||||
assert_eq!(a.to_string(), b.to_string());
|
||||
assert_eq!(format!("{a}"), format!("{b}"));
|
||||
assert_eq!(format!("{}", a == b), "true");
|
||||
assert_eq!(format!("{}", cmp_in(a, b)), "true");
|
||||
assert_eq!(format!("{}", cmp(a, b)), "true");
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ fn main() {
|
|||
// It's not zero, which means `a` and `b` are not equal.
|
||||
assert_ne!(i, 0);
|
||||
// But it looks like zero...
|
||||
assert_eq!(i.to_string(), "0");
|
||||
assert_eq!(format!("{i}"), "0");
|
||||
// ...and now it *is* zero?
|
||||
assert_eq!(i, 0);
|
||||
// So `a` and `b` are equal after all?
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ fn main() {
|
|||
let v = 0;
|
||||
ptr::from_ref(&v).addr()
|
||||
};
|
||||
assert_eq!(a.to_string(), b.to_string());
|
||||
assert_eq!(format!("{a}"), format!("{b}"));
|
||||
assert_eq!(format!("{}", a == b), "true");
|
||||
assert_eq!(format!("{}", cmp_in(a, b)), "true");
|
||||
assert_eq!(format!("{}", cmp(a, b)), "true");
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ fn main() {
|
|||
// It's not zero, which means `a` and `b` are not equal.
|
||||
assert_ne!(i, 0);
|
||||
// But it looks like zero...
|
||||
assert_eq!(i.to_string(), "0");
|
||||
assert_eq!(format!("{i}"), "0");
|
||||
// ...and now it *is* zero?
|
||||
assert_eq!(i, 0);
|
||||
// So `a` and `b` are equal after all?
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue