Skip to content

Commit fe7cdd8

Browse files
Small optimization for integers Display implementation
1 parent 2ccafed commit fe7cdd8

File tree

1 file changed

+131
-59
lines changed

1 file changed

+131
-59
lines changed

Diff for: library/core/src/fmt/num.rs

+131-59
Original file line numberDiff line numberDiff line change
@@ -211,11 +211,47 @@ static DEC_DIGITS_LUT: &[u8; 200] = b"0001020304050607080910111213141516171819\
211211
8081828384858687888990919293949596979899";
212212

213213
macro_rules! impl_Display {
214-
($($t:ident),* as $u:ident via $conv_fn:ident named $name:ident) => {
214+
($($t:ident => $size:literal $(as $positive:ident in $other:ident)? => named $name:ident,)* ; as $u:ident via $conv_fn:ident named $gen_name:ident) => {
215+
216+
$(
217+
#[stable(feature = "rust1", since = "1.0.0")]
218+
impl fmt::Display for $t {
219+
#[allow(unused_comparisons)]
220+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221+
// If it's a signed integer.
222+
$(
223+
let is_nonnegative = *self >= 0;
224+
225+
#[cfg(not(feature = "optimize_for_size"))]
226+
{
227+
if !is_nonnegative {
228+
// convert the negative num to positive by summing 1 to it's 2 complement
229+
return $other((!self as $positive + 1), false, f);
230+
}
231+
}
232+
#[cfg(feature = "optimize_for_size")]
233+
{
234+
if !is_nonnegative {
235+
// convert the negative num to positive by summing 1 to it's 2 complement
236+
return $other((!self.$conv_fn()).wrapping_add(1), false, f);
237+
}
238+
}
239+
)?
240+
// If it's an unsigned integer.
241+
#[cfg(not(feature = "optimize_for_size"))]
242+
{
243+
$name(*self, true, f)
244+
}
245+
#[cfg(feature = "optimize_for_size")]
246+
{
247+
$gen_name(*self, true, f)
248+
}
249+
}
250+
}
251+
215252
#[cfg(not(feature = "optimize_for_size"))]
216-
fn $name(mut n: $u, is_nonnegative: bool, f: &mut fmt::Formatter<'_>) -> fmt::Result {
217-
// 2^128 is about 3*10^38, so 39 gives an extra byte of space
218-
let mut buf = [MaybeUninit::<u8>::uninit(); 39];
253+
fn $name(mut n: $t, is_nonnegative: bool, f: &mut fmt::Formatter<'_>) -> fmt::Result {
254+
let mut buf = [MaybeUninit::<u8>::uninit(); $size];
219255
let mut curr = buf.len();
220256
let buf_ptr = MaybeUninit::slice_as_mut_ptr(&mut buf);
221257
let lut_ptr = DEC_DIGITS_LUT.as_ptr();
@@ -229,58 +265,64 @@ macro_rules! impl_Display {
229265
// is safe to access.
230266
unsafe {
231267
// need at least 16 bits for the 4-characters-at-a-time to work.
232-
assert!(crate::mem::size_of::<$u>() >= 2);
233-
234-
// eagerly decode 4 characters at a time
235-
while n >= 10000 {
236-
let rem = (n % 10000) as usize;
237-
n /= 10000;
238-
239-
let d1 = (rem / 100) << 1;
240-
let d2 = (rem % 100) << 1;
241-
curr -= 4;
242-
243-
// We are allowed to copy to `buf_ptr[curr..curr + 3]` here since
244-
// otherwise `curr < 0`. But then `n` was originally at least `10000^10`
245-
// which is `10^40 > 2^128 > n`.
246-
ptr::copy_nonoverlapping(lut_ptr.add(d1), buf_ptr.add(curr), 2);
247-
ptr::copy_nonoverlapping(lut_ptr.add(d2), buf_ptr.add(curr + 2), 2);
268+
#[allow(overflowing_literals)]
269+
#[allow(unused_comparisons)]
270+
// This block should be removed for smaller types at compile time so it
271+
// should be ok.
272+
if core::mem::size_of::<$t>() >= 2 {
273+
// eagerly decode 4 characters at a time
274+
while n >= 10000 {
275+
let rem = (n % 10000) as u16;
276+
n /= 10000;
277+
278+
let d1 = (rem / 100) << 1;
279+
let d2 = (rem % 100) << 1;
280+
curr -= 4;
281+
282+
// We are allowed to copy to `buf_ptr[curr..curr + 3]` here since
283+
// otherwise `curr < 0`. But then `n` was originally at least `10000^10`
284+
// which is `10^40 > 2^128 > n`.
285+
ptr::copy_nonoverlapping(lut_ptr.add(d1 as usize), buf_ptr.add(curr), 2);
286+
ptr::copy_nonoverlapping(lut_ptr.add(d2 as usize), buf_ptr.add(curr + 2), 2);
287+
}
248288
}
249289

250290
// if we reach here numbers are <= 9999, so at most 4 chars long
251-
let mut n = n as usize; // possibly reduce 64bit math
291+
let mut n = n as u16; // possibly reduce 64bit math
252292

253293
// decode 2 more chars, if > 2 chars
254294
if n >= 100 {
255295
let d1 = (n % 100) << 1;
256296
n /= 100;
257297
curr -= 2;
258-
ptr::copy_nonoverlapping(lut_ptr.add(d1), buf_ptr.add(curr), 2);
298+
ptr::copy_nonoverlapping(lut_ptr.add(d1 as usize), buf_ptr.add(curr), 2);
259299
}
260300

301+
// if we reach here numbers are <= 100, so at most 2 chars long
302+
// The biggest it can be is 99, and 99 << 1 == 198, so a `u8` is enough.
303+
let n = n as u8;
261304
// decode last 1 or 2 chars
262305
if n < 10 {
263306
curr -= 1;
264-
*buf_ptr.add(curr) = (n as u8) + b'0';
307+
*buf_ptr.add(curr) = n + b'0';
265308
} else {
266309
let d1 = n << 1;
267310
curr -= 2;
268-
ptr::copy_nonoverlapping(lut_ptr.add(d1), buf_ptr.add(curr), 2);
311+
ptr::copy_nonoverlapping(lut_ptr.add(d1 as usize), buf_ptr.add(curr), 2);
269312
}
270313
}
271314

272315
// SAFETY: `curr` > 0 (since we made `buf` large enough), and all the chars are valid
273316
// UTF-8 since `DEC_DIGITS_LUT` is
274317
let buf_slice = unsafe {
275-
str::from_utf8_unchecked(
318+
core::str::from_utf8_unchecked(
276319
slice::from_raw_parts(buf_ptr.add(curr), buf.len() - curr))
277320
};
278321
f.pad_integral(is_nonnegative, "", buf_slice)
279-
}
322+
})*
280323

281324
#[cfg(feature = "optimize_for_size")]
282-
fn $name(mut n: $u, is_nonnegative: bool, f: &mut fmt::Formatter<'_>) -> fmt::Result {
283-
// 2^128 is about 3*10^38, so 39 gives an extra byte of space
325+
fn $gen_name(mut n: $u, is_nonnegative: bool, f: &mut fmt::Formatter<'_>) -> fmt::Result {
284326
let mut buf = [MaybeUninit::<u8>::uninit(); 39];
285327
let mut curr = buf.len();
286328
let buf_ptr = MaybeUninit::slice_as_mut_ptr(&mut buf);
@@ -309,27 +351,12 @@ macro_rules! impl_Display {
309351
};
310352
f.pad_integral(is_nonnegative, "", buf_slice)
311353
}
312-
313-
$(#[stable(feature = "rust1", since = "1.0.0")]
314-
impl fmt::Display for $t {
315-
#[allow(unused_comparisons)]
316-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
317-
let is_nonnegative = *self >= 0;
318-
let n = if is_nonnegative {
319-
self.$conv_fn()
320-
} else {
321-
// convert the negative num to positive by summing 1 to it's 2 complement
322-
(!self.$conv_fn()).wrapping_add(1)
323-
};
324-
$name(n, is_nonnegative, f)
325-
}
326-
})*
327354
};
328355
}
329356

330357
macro_rules! impl_Exp {
331-
($($t:ident),* as $u:ident via $conv_fn:ident named $name:ident) => {
332-
fn $name(
358+
($($t:ident => $size:literal,)* ; as $u:ident via $conv_fn:ident named $name:ident) => {
359+
fn $name<const SIZE: usize>(
333360
mut n: $u,
334361
is_nonnegative: bool,
335362
upper: bool,
@@ -377,10 +404,9 @@ macro_rules! impl_Exp {
377404
(n, exponent, exponent, added_precision)
378405
};
379406

380-
// 39 digits (worst case u128) + . = 40
381407
// Since `curr` always decreases by the number of digits copied, this means
382408
// that `curr >= 0`.
383-
let mut buf = [MaybeUninit::<u8>::uninit(); 40];
409+
let mut buf = [MaybeUninit::<u8>::uninit(); SIZE];
384410
let mut curr = buf.len(); //index for buf
385411
let buf_ptr = MaybeUninit::slice_as_mut_ptr(&mut buf);
386412
let lut_ptr = DEC_DIGITS_LUT.as_ptr();
@@ -398,7 +424,7 @@ macro_rules! impl_Exp {
398424
exponent += 2;
399425
}
400426
// n is <= 99, so at most 2 chars long
401-
let mut n = n as isize; // possibly reduce 64bit math
427+
let mut n = n as i8; // possibly reduce 64bit math
402428
// decode second-to-last character
403429
if n >= 10 {
404430
curr -= 1;
@@ -475,7 +501,7 @@ macro_rules! impl_Exp {
475501
// convert the negative num to positive by summing 1 to it's 2 complement
476502
(!self.$conv_fn()).wrapping_add(1)
477503
};
478-
$name(n, is_nonnegative, false, f)
504+
$name::<$size>(n, is_nonnegative, false, f)
479505
}
480506
})*
481507
$(
@@ -490,7 +516,7 @@ macro_rules! impl_Exp {
490516
// convert the negative num to positive by summing 1 to it's 2 complement
491517
(!self.$conv_fn()).wrapping_add(1)
492518
};
493-
$name(n, is_nonnegative, true, f)
519+
$name::<$size>(n, is_nonnegative, true, f)
494520
}
495521
})*
496522
};
@@ -502,24 +528,70 @@ macro_rules! impl_Exp {
502528
mod imp {
503529
use super::*;
504530
impl_Display!(
505-
i8, u8, i16, u16, i32, u32, i64, u64, usize, isize
506-
as u64 via to_u64 named fmt_u64
531+
i8 => 4 as u8 in fmt_u8 => named fmt_i8,
532+
u8 => 3 => named fmt_u8,
533+
i16 => 6 as u16 in fmt_u16 => named fmt_i16,
534+
u16 => 5 => named fmt_u16,
535+
i32 => 10 as u32 in fmt_u32 => named fmt_i32,
536+
u32 => 9 => named fmt_u32,
537+
i64 => 20 as u64 in fmt_u64 => named fmt_i64,
538+
u64 => 20 => named fmt_u64,
539+
isize => 20 as u64 in fmt_u64 => named fmt_isize,
540+
usize => 20 => named fmt_usize,
541+
; as u64 via to_u64 named fmt_u64
507542
);
508543
impl_Exp!(
509-
i8, u8, i16, u16, i32, u32, i64, u64, usize, isize
510-
as u64 via to_u64 named exp_u64
544+
i8 => 5,
545+
u8 => 4,
546+
i16 => 7,
547+
u16 => 6,
548+
i32 => 11,
549+
u32 => 10,
550+
i64 => 21,
551+
u64 => 21,
552+
isize => 21,
553+
usize => 21,
554+
; as u64 via to_u64 named exp_u64
511555
);
512556
}
513557

514558
#[cfg(not(any(target_pointer_width = "64", target_arch = "wasm32")))]
515559
mod imp {
516560
use super::*;
517-
impl_Display!(i8, u8, i16, u16, i32, u32, isize, usize as u32 via to_u32 named fmt_u32);
518-
impl_Display!(i64, u64 as u64 via to_u64 named fmt_u64);
519-
impl_Exp!(i8, u8, i16, u16, i32, u32, isize, usize as u32 via to_u32 named exp_u32);
520-
impl_Exp!(i64, u64 as u64 via to_u64 named exp_u64);
561+
impl_Display!(
562+
i8 => 4 as u8 in fmt_u8 => named fmt_i8,
563+
u8 => 3 => named fmt_u8,
564+
i16 => 6 as u16 in fmt_u16 => named fmt_i16,
565+
u16 => 5 => named fmt_u16,
566+
i32 => 10 as u32 in fmt_u32 => named fmt_i32,
567+
u32 => 9 => named fmt_u32,
568+
isize => 10 as usize in fmt_usize => named fmt_isize,
569+
usize => 9 => named fmt_usize,
570+
; as u32 via to_u32 named fmt_u32);
571+
impl_Display!(
572+
i64 => 20 as u64 in fmt_u64 => named fmt_i64,
573+
u64 => 20 => named fmt_u64,
574+
; as u64 via to_u64 named fmt_u64);
575+
576+
impl_Exp!(
577+
i8 => 5,
578+
u8 => 4,
579+
i16 => 7,
580+
u16 => 6,
581+
i32 => 11,
582+
u32 => 10,
583+
isize => 11,
584+
usize => 10,
585+
; as u32 via to_u32 named exp_u32);
586+
impl_Exp!(
587+
i64 => 21,
588+
u64 => 21,
589+
; as u64 via to_u64 named exp_u64);
521590
}
522-
impl_Exp!(i128, u128 as u128 via to_u128 named exp_u128);
591+
impl_Exp!(
592+
i128 => 40,
593+
u128 => 39,
594+
; as u128 via to_u128 named exp_u128);
523595

524596
/// Helper function for writing a u64 into `buf` going from last to first, with `curr`.
525597
fn parse_u64_into<const N: usize>(mut n: u64, buf: &mut [MaybeUninit<u8>; N], curr: &mut usize) {

0 commit comments

Comments
 (0)