Skip to content

Commit 2062ce4

Browse files
committed
filters: optimize FilesizeFormatFilter
1 parent 09466d8 commit 2062ce4

File tree

1 file changed

+69
-39
lines changed

1 file changed

+69
-39
lines changed

rinja/src/filters/humansize.rs

Lines changed: 69 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
use std::convert::Infallible;
22
use std::fmt;
3+
use std::mem::MaybeUninit;
4+
use std::str::from_utf8_unchecked;
5+
6+
use super::FastWritable;
37

48
/// Returns adequate string representation (in KB, ..) of number of bytes
59
///
@@ -26,59 +30,85 @@ pub fn filesizeformat(b: f32) -> Result<FilesizeFormatFilter, Infallible> {
2630
#[derive(Debug, Clone, Copy)]
2731
pub struct FilesizeFormatFilter(f32);
2832

29-
struct NbAndDecimal(u32);
30-
impl NbAndDecimal {
31-
fn new(nb: f32) -> Self {
32-
// `nb` will never be bigger than 999_999 so we're fine with usize.
33-
Self(nb as _)
34-
}
35-
}
36-
impl fmt::Display for NbAndDecimal {
33+
impl fmt::Display for FilesizeFormatFilter {
34+
#[inline]
3735
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38-
let sub = self.0 % 1_000 / 10;
39-
if sub != 0 {
40-
if sub < 10 {
41-
f.write_fmt(format_args!("{}.0{sub}", self.0 / 1_000))
42-
} else {
43-
f.write_fmt(format_args!("{}.{sub}", self.0 / 1_000))
44-
}
45-
} else {
46-
f.write_fmt(format_args!("{}", self.0 / 1_000))
47-
}
36+
self.write_into(f)
4837
}
4938
}
5039

51-
impl fmt::Display for FilesizeFormatFilter {
52-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40+
impl FastWritable for FilesizeFormatFilter {
41+
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> fmt::Result {
5342
if self.0 < 1e3 {
54-
return f.write_fmt(format_args!("{} B", self.0));
55-
}
56-
for (unit, max, divider) in [
57-
(" KB", 1e6, 1.),
58-
(" MB", 1e9, 1e3),
59-
(" GB", 1e12, 1e6),
60-
(" TB", 1e15, 1e9),
61-
(" PB", 1e18, 1e12),
62-
(" EB", 1e21, 1e15),
63-
(" ZB", 1e24, 1e18),
64-
] {
65-
if self.0 < max {
66-
f.write_fmt(format_args!("{}", NbAndDecimal::new(self.0 / divider)))?;
67-
return f.write_str(unit);
68-
}
43+
(self.0 as u32).write_into(dest)?;
44+
dest.write_str(" B")
45+
} else if let Some((prefix, factor)) = SI_PREFIXES
46+
.iter()
47+
.copied()
48+
.find_map(|(prefix_factor, max)| (self.0 < max).then_some(prefix_factor))
49+
{
50+
// u32 is big enough to hold the number 999_999
51+
let scaled = (self.0 * factor) as u32;
52+
(scaled / 100).write_into(dest)?;
53+
dest.write_str(format_frac(&mut MaybeUninit::uninit(), prefix, scaled))
54+
} else {
55+
too_big(self.0, dest)
6956
}
70-
f.write_fmt(format_args!("{} YB", NbAndDecimal::new(self.0 / 1e21)))
7157
}
7258
}
7359

60+
/// Formats `buffer` to contain the decimal point, decimal places and unit
61+
fn format_frac(buffer: &mut MaybeUninit<[u8; 8]>, prefix: u8, scaled: u32) -> &str {
62+
// LLVM generates better byte code for register sized buffers, so we add some NULs
63+
let buffer = buffer.write(*b"..0 kB\0\0");
64+
buffer[4] = prefix;
65+
66+
let frac = scaled % 100;
67+
let buffer = if frac == 0 {
68+
&buffer[3..6]
69+
} else if frac % 10 == 0 {
70+
// the decimal separator '.' is already contained in buffer[1]
71+
buffer[2] = b'0' + (frac / 10) as u8;
72+
&buffer[1..6]
73+
} else {
74+
// the decimal separator '.' is already contained in buffer[0]
75+
buffer[1] = b'0' + (frac / 10) as u8;
76+
buffer[2] = b'0' + (frac % 10) as u8;
77+
&buffer[0..6]
78+
};
79+
// SAFETY: we know that the buffer contains only ASCII data
80+
unsafe { from_utf8_unchecked(buffer) }
81+
}
82+
83+
#[cold]
84+
fn too_big<W: fmt::Write + ?Sized>(value: f32, dest: &mut W) -> fmt::Result {
85+
// the value exceeds 999 QB, so we omit the decimal places
86+
write!(dest, "{:.0} QB", value / 1e30)
87+
}
88+
89+
/// `((si_prefix, factor), limit)`, the factor is offset by 10**2 to account for 2 decimal places
90+
const SI_PREFIXES: &[((u8, f32), f32)] = &[
91+
((b'k', 1e-1), 1e6),
92+
((b'M', 1e-4), 1e9),
93+
((b'G', 1e-7), 1e12),
94+
((b'T', 1e-10), 1e15),
95+
((b'P', 1e-13), 1e18),
96+
((b'E', 1e-16), 1e21),
97+
((b'Z', 1e-19), 1e24),
98+
((b'Y', 1e-22), 1e27),
99+
((b'R', 1e-25), 1e30),
100+
((b'Q', 1e-28), 1e33),
101+
];
102+
74103
#[test]
75104
#[allow(clippy::needless_borrows_for_generic_args)]
76105
fn test_filesizeformat() {
77106
assert_eq!(filesizeformat(0.).unwrap().to_string(), "0 B");
78107
assert_eq!(filesizeformat(999.).unwrap().to_string(), "999 B");
79-
assert_eq!(filesizeformat(1000.).unwrap().to_string(), "1 KB");
80-
assert_eq!(filesizeformat(1023.).unwrap().to_string(), "1.02 KB");
81-
assert_eq!(filesizeformat(1024.).unwrap().to_string(), "1.02 KB");
108+
assert_eq!(filesizeformat(1000.).unwrap().to_string(), "1 kB");
109+
assert_eq!(filesizeformat(1023.).unwrap().to_string(), "1.02 kB");
110+
assert_eq!(filesizeformat(1024.).unwrap().to_string(), "1.02 kB");
111+
assert_eq!(filesizeformat(1100.).unwrap().to_string(), "1.1 kB");
82112
assert_eq!(filesizeformat(9_499_014.).unwrap().to_string(), "9.49 MB");
83113
assert_eq!(
84114
filesizeformat(954_548_589.2).unwrap().to_string(),

0 commit comments

Comments
 (0)