Skip to content

Commit f9aaddf

Browse files
committed
cat: Switch to uucore's fast_inc_one
Instead of reimplementing a string increment function, use the one in uucore. Also, performance is around 5% better.
1 parent 764514b commit f9aaddf

File tree

2 files changed

+46
-45
lines changed

2 files changed

+46
-45
lines changed

src/uu/cat/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ path = "src/cat.rs"
2121
clap = { workspace = true }
2222
memchr = { workspace = true }
2323
thiserror = { workspace = true }
24-
uucore = { workspace = true, features = ["fs", "pipes"] }
24+
uucore = { workspace = true, features = ["fast-inc", "fs", "pipes"] }
2525

2626
[target.'cfg(unix)'.dependencies]
2727
nix = { workspace = true }

src/uu/cat/src/cat.rs

Lines changed: 45 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use thiserror::Error;
2424
use uucore::display::Quotable;
2525
use uucore::error::UResult;
2626
use uucore::fs::FileInformation;
27-
use uucore::{format_usage, help_about, help_usage};
27+
use uucore::{fast_inc::fast_inc_one, format_usage, help_about, help_usage};
2828

2929
/// Linux splice support
3030
#[cfg(any(target_os = "linux", target_os = "android"))]
@@ -35,59 +35,45 @@ const ABOUT: &str = help_about!("cat.md");
3535

3636
struct LineNumber {
3737
buf: Vec<u8>,
38+
print_start: usize,
39+
num_start: usize,
40+
num_end: usize,
3841
}
3942

4043
// Logic to store a string for the line number. Manually incrementing the value
4144
// represented in a buffer like this is significantly faster than storing
4245
// a `usize` and using the standard Rust formatting macros to format a `usize`
4346
// to a string each time it's needed.
44-
// String is initialized to " 1\t" and incremented each time `increment` is
45-
// called. When the value overflows the range storable in the buffer, a b'1' is
46-
// prepended and the counting continues.
47+
// Buffer is initialized to " 1\t" and incremented each time `increment` is
48+
// called, using uucore's fast_inc function that operates on strings.
4749
impl LineNumber {
4850
fn new() -> Self {
51+
// 1024-digit long line number should be enough to run `cat` for the lifetime of the universe.
52+
let size = 1024;
53+
let mut buf = vec![b'0'; size];
54+
55+
let init_str = " 1\t";
56+
let print_start = buf.len() - init_str.len();
57+
let num_start = buf.len() - 2;
58+
let num_end = buf.len() - 1;
59+
60+
buf[print_start..].copy_from_slice(init_str.as_bytes());
61+
4962
LineNumber {
50-
// Initialize buf to b" 1\t"
51-
buf: Vec::from(b" 1\t"),
63+
buf,
64+
print_start,
65+
num_start,
66+
num_end,
5267
}
5368
}
5469

5570
fn increment(&mut self) {
56-
// skip(1) to avoid the \t in the last byte.
57-
for ascii_digit in self.buf.iter_mut().rev().skip(1) {
58-
// Working from the least-significant digit, increment the number in the buffer.
59-
// If we hit anything other than a b'9' we can break since the next digit is
60-
// unaffected.
61-
// Also note that if we hit a b' ', we can think of that as a 0 and increment to b'1'.
62-
// If/else here is faster than match (as measured with some benchmarking Apr-2025),
63-
// probably since we can prioritize most likely digits first.
64-
if (b'0'..=b'8').contains(ascii_digit) {
65-
*ascii_digit += 1;
66-
break;
67-
} else if b'9' == *ascii_digit {
68-
*ascii_digit = b'0';
69-
} else {
70-
assert_eq!(*ascii_digit, b' ');
71-
*ascii_digit = b'1';
72-
break;
73-
}
74-
}
75-
if self.buf[0] == b'0' {
76-
// This implies we've overflowed. In this case the buffer will be
77-
// [b'0', b'0', ..., b'0', b'\t'].
78-
// For debugging, the following logic would assert that to be the case.
79-
// assert_eq!(*self.buf.last().unwrap(), b'\t');
80-
// for ascii_digit in self.buf.iter_mut().rev().skip(1) {
81-
// assert_eq!(*ascii_digit, b'0');
82-
// }
83-
84-
// All we need to do is prepend a b'1' and we're good.
85-
self.buf.insert(0, b'1');
86-
}
71+
self.num_start = fast_inc_one(self.buf.as_mut_slice(), self.num_start, self.num_end);
72+
self.print_start = self.print_start.min(self.num_start);
8773
}
8874

8975
fn write(&self, writer: &mut impl Write) -> io::Result<()> {
90-
writer.write_all(&self.buf)
76+
writer.write_all(&self.buf[self.print_start..])
9177
}
9278
}
9379

@@ -804,21 +790,36 @@ mod tests {
804790
#[test]
805791
fn test_incrementing_string() {
806792
let mut incrementing_string = super::LineNumber::new();
807-
assert_eq!(b" 1\t", incrementing_string.buf.as_slice());
793+
assert_eq!(
794+
b" 1\t",
795+
&incrementing_string.buf[incrementing_string.print_start..]
796+
);
808797
incrementing_string.increment();
809-
assert_eq!(b" 2\t", incrementing_string.buf.as_slice());
798+
assert_eq!(
799+
b" 2\t",
800+
&incrementing_string.buf[incrementing_string.print_start..]
801+
);
810802
// Run through to 100
811803
for _ in 3..=100 {
812804
incrementing_string.increment();
813805
}
814-
assert_eq!(b" 100\t", incrementing_string.buf.as_slice());
806+
assert_eq!(
807+
b" 100\t",
808+
&incrementing_string.buf[incrementing_string.print_start..]
809+
);
815810
// Run through until we overflow the original size.
816811
for _ in 101..=1_000_000 {
817812
incrementing_string.increment();
818813
}
819-
// Confirm that the buffer expands when we overflow the original size.
820-
assert_eq!(b"1000000\t", incrementing_string.buf.as_slice());
814+
// Confirm that the start position moves when we overflow the original size.
815+
assert_eq!(
816+
b"1000000\t",
817+
&incrementing_string.buf[incrementing_string.print_start..]
818+
);
821819
incrementing_string.increment();
822-
assert_eq!(b"1000001\t", incrementing_string.buf.as_slice());
820+
assert_eq!(
821+
b"1000001\t",
822+
&incrementing_string.buf[incrementing_string.print_start..]
823+
);
823824
}
824825
}

0 commit comments

Comments
 (0)