Skip to content

Commit 1087590

Browse files
committed
Add benchmarks for fmt u128
This tests both when there is the max amount of work(all characters used) And least amount of work(1 character used)
1 parent 19e56cb commit 1087590

File tree

2 files changed

+82
-47
lines changed

2 files changed

+82
-47
lines changed

library/core/benches/fmt.rs

+29
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,32 @@ fn write_str_macro_debug(bh: &mut Bencher) {
108108
}
109109
});
110110
}
111+
112+
#[bench]
113+
fn write_u128_max(bh: &mut Bencher) {
114+
bh.iter(|| {
115+
std::hint::black_box(format!("{}", u128::MAX));
116+
});
117+
}
118+
119+
#[bench]
120+
fn write_u128_min(bh: &mut Bencher) {
121+
bh.iter(|| {
122+
let s = format!("{}", 0u128);
123+
std::hint::black_box(s);
124+
});
125+
}
126+
127+
#[bench]
128+
fn write_u64_max(bh: &mut Bencher) {
129+
bh.iter(|| {
130+
std::hint::black_box(format!("{}", u64::MAX));
131+
});
132+
}
133+
134+
#[bench]
135+
fn write_u64_min(bh: &mut Bencher) {
136+
bh.iter(|| {
137+
std::hint::black_box(format!("{}", 0u64));
138+
});
139+
}

library/core/src/fmt/num.rs

+53-47
Original file line numberDiff line numberDiff line change
@@ -474,20 +474,22 @@ mod imp {
474474
impl_Exp!(i8, u8, i16, u16, i32, u32, isize, usize as u32 via to_u32 named exp_u32);
475475
impl_Exp!(i64, u64 as u64 via to_u64 named exp_u64);
476476
}
477-
478-
// impl_Display!(i128, u128 as u128 via to_u128 named fmt_u128);
479477
impl_Exp!(i128, u128 as u128 via to_u128 named exp_u128);
480478

481-
fn fmt_u64_2<const N: usize>(mut rem: u64, buf: &mut [MaybeUninit<u8>; N], curr: &mut isize) {
479+
/// Helper function for writing a u64 into `buf` going from last to first, with `curr`.
480+
fn parse_u64_into<const N: usize>(mut n: u64, buf: &mut [MaybeUninit<u8>; N], curr: &mut isize) {
482481
let buf_ptr = MaybeUninit::first_ptr_mut(buf);
483482
let lut_ptr = DEC_DIGITS_LUT.as_ptr();
483+
assert!(*curr > 19);
484484

485485
// SAFETY:
486-
// FIXME(fill this in after reviews)
486+
// Writes at most 19 characters into the buffer. Guaranteed that any ptr into LUT is at most
487+
// 198, so will never OOB. There is a check above that there are at least 19 characters
488+
// remaining.
487489
unsafe {
488-
if rem >= 1e16 as u64 {
489-
let to_parse = rem % 1e16 as u64;
490-
rem /= 1e16 as u64;
490+
if n >= 1e16 as u64 {
491+
let to_parse = n % 1e16 as u64;
492+
n /= 1e16 as u64;
491493

492494
// Some of these are nops but it looks more elegant this way.
493495
let d1 = ((to_parse / 1e14 as u64) % 100) << 1;
@@ -510,9 +512,9 @@ fn fmt_u64_2<const N: usize>(mut rem: u64, buf: &mut [MaybeUninit<u8>; N], curr:
510512
ptr::copy_nonoverlapping(lut_ptr.offset(d7 as isize), buf_ptr.offset(*curr + 12), 2);
511513
ptr::copy_nonoverlapping(lut_ptr.offset(d8 as isize), buf_ptr.offset(*curr + 14), 2);
512514
}
513-
if rem >= 1e8 as u64 {
514-
let to_parse = rem % 1e8 as u64;
515-
rem /= 1e8 as u64;
515+
if n >= 1e8 as u64 {
516+
let to_parse = n % 1e8 as u64;
517+
n /= 1e8 as u64;
516518

517519
// Some of these are nops but it looks more elegant this way.
518520
let d1 = ((to_parse / 1e6 as u64) % 100) << 1;
@@ -526,11 +528,11 @@ fn fmt_u64_2<const N: usize>(mut rem: u64, buf: &mut [MaybeUninit<u8>; N], curr:
526528
ptr::copy_nonoverlapping(lut_ptr.offset(d3 as isize), buf_ptr.offset(*curr + 4), 2);
527529
ptr::copy_nonoverlapping(lut_ptr.offset(d4 as isize), buf_ptr.offset(*curr + 6), 2);
528530
}
529-
// `rem` < 1e8 < (1 << 32)
530-
let mut rem = rem as u32;
531-
if rem >= 1e4 as u32 {
532-
let to_parse = rem % 1e4 as u32;
533-
rem /= 1e4 as u32;
531+
// `n` < 1e8 < (1 << 32)
532+
let mut n = n as u32;
533+
if n >= 1e4 as u32 {
534+
let to_parse = n % 1e4 as u32;
535+
n /= 1e4 as u32;
534536

535537
let d1 = (to_parse / 100) << 1;
536538
let d2 = (to_parse % 100) << 1;
@@ -540,21 +542,21 @@ fn fmt_u64_2<const N: usize>(mut rem: u64, buf: &mut [MaybeUninit<u8>; N], curr:
540542
ptr::copy_nonoverlapping(lut_ptr.offset(d2 as isize), buf_ptr.offset(*curr + 2), 2);
541543
}
542544

543-
// `rem` < 1e4 < (1 << 16)
544-
let mut rem = rem as u16;
545-
if rem >= 100 {
546-
let d1 = (rem % 100) << 1;
547-
rem /= 100;
545+
// `n` < 1e4 < (1 << 16)
546+
let mut n = n as u16;
547+
if n >= 100 {
548+
let d1 = (n % 100) << 1;
549+
n /= 100;
548550
*curr -= 2;
549551
ptr::copy_nonoverlapping(lut_ptr.offset(d1 as isize), buf_ptr.offset(*curr), 2);
550552
}
551553

552554
// decode last 1 or 2 chars
553-
if rem < 10 {
555+
if n < 10 {
554556
*curr -= 1;
555-
*buf_ptr.offset(*curr) = (rem as u8) + b'0';
557+
*buf_ptr.offset(*curr) = (n as u8) + b'0';
556558
} else {
557-
let d1 = rem << 1;
559+
let d1 = n << 1;
558560
*curr -= 2;
559561
ptr::copy_nonoverlapping(lut_ptr.offset(d1 as isize), buf_ptr.offset(*curr), 2);
560562
}
@@ -564,7 +566,7 @@ fn fmt_u64_2<const N: usize>(mut rem: u64, buf: &mut [MaybeUninit<u8>; N], curr:
564566
#[stable(feature = "rust1", since = "1.0.0")]
565567
impl fmt::Display for u128 {
566568
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
567-
fmt_u128_2(*self, true, f)
569+
fmt_u128(*self, true, f)
568570
}
569571
}
570572

@@ -578,38 +580,41 @@ impl fmt::Display for i128 {
578580
// convert the negative num to positive by summing 1 to it's 2 complement
579581
(!self.to_u128()).wrapping_add(1)
580582
};
581-
fmt_u128_2(n, is_nonnegative, f)
583+
fmt_u128(n, is_nonnegative, f)
582584
}
583585
}
584586

585-
#[allow(dead_code)]
586-
fn fmt_u128_2(n: u128, is_nonnegative: bool, f: &mut fmt::Formatter<'_>) -> fmt::Result {
587+
/// Specialized optimization for u128. Instead of taking two items at a time, it splits
588+
/// into at most 2 u64s, and then chunks by 10e16, 10e8, 10e4, 10e2, and then 10e1.
589+
/// It also has to handle 1 last item, as 10^40 > 2^128 > 10^39, whereas
590+
/// 10^20 > 2^64 > 10^19.
591+
fn fmt_u128(n: u128, is_nonnegative: bool, f: &mut fmt::Formatter<'_>) -> fmt::Result {
587592
// 2^128 is about 3*10^38, so 39 gives an extra byte of space
588593
let mut buf = [MaybeUninit::<u8>::uninit(); 39];
589594
let mut curr = buf.len() as isize;
590595
let buf_ptr = MaybeUninit::first_ptr_mut(&mut buf);
591596

592-
// SAFETY: Since `d1` and `d2` are always less than or equal to `198`, we
593-
// can copy from `lut_ptr[d1..d1 + 1]` and `lut_ptr[d2..d2 + 1]`. To show
594-
// that it's OK to copy into `buf_ptr`, notice that at the beginning
595-
// `curr == buf.len() == 39 > log(n)` since `n < 2^128 < 10^39`, and at
596-
// each step this is kept the same as `n` is divided. Since `n` is always
597-
// non-negative, this means that `curr > 0` so `buf_ptr[curr..curr + 1]`
598-
// is safe to access.
599-
unsafe {
600-
let (n, rem) = udiv_1e9(n);
601-
fmt_u64_2(rem, &mut buf, &mut curr);
597+
let (n, rem) = udiv_1e19(n);
598+
parse_u64_into(rem, &mut buf, &mut curr);
602599

603-
if n != 0 {
604-
// 0 pad up to point
605-
let target = (buf.len() - 19) as isize;
600+
if n != 0 {
601+
// 0 pad up to point
602+
let target = (buf.len() - 19) as isize;
603+
// SAFETY: Guaranteed that we wrote at most 19 bytes, and there must be space
604+
// remaining since it has length 39
605+
unsafe {
606606
ptr::write_bytes(buf_ptr.offset(target), b'0', (curr - target) as usize);
607-
curr = target;
608-
let (n, rem) = udiv_1e9(n);
609-
fmt_u64_2(rem, &mut buf, &mut curr);
610-
// Should this following branch be annotated with unlikely?
611-
if n != 0 {
612-
let target = (buf.len() - 38) as isize;
607+
}
608+
curr = target;
609+
610+
let (n, rem) = udiv_1e19(n);
611+
parse_u64_into(rem, &mut buf, &mut curr);
612+
// Should this following branch be annotated with unlikely?
613+
if n != 0 {
614+
let target = (buf.len() - 38) as isize;
615+
// SAFETY: At this point we wrote at most 38 bytes, pad up to that point,
616+
// There can only be at most 1 digit remaining.
617+
unsafe {
613618
ptr::write_bytes(buf_ptr.offset(target), b'0', (curr - target) as usize);
614619
curr = target - 1;
615620
*buf_ptr.offset(curr) = (n as u8) + b'0';
@@ -628,7 +633,8 @@ fn fmt_u128_2(n: u128, is_nonnegative: bool, f: &mut fmt::Formatter<'_>) -> fmt:
628633
f.pad_integral(is_nonnegative, "", buf_slice)
629634
}
630635

631-
fn udiv_1e9(n: u128) -> (u128, u64) {
636+
/// Partition of `n` into n > 1e19 and rem <= 1e19
637+
fn udiv_1e19(n: u128) -> (u128, u64) {
632638
const DIV: u64 = 1e19 as u64;
633639
let high = (n >> 64) as u64;
634640
if high == 0 {

0 commit comments

Comments
 (0)