1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-29 12:07:46 +00:00

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.
This commit is contained in:
Nicolas Boichat 2025-04-05 10:08:44 +02:00
parent 764514bf22
commit f9aaddfd3d
2 changed files with 46 additions and 45 deletions

View file

@ -21,7 +21,7 @@ path = "src/cat.rs"
clap = { workspace = true } clap = { workspace = true }
memchr = { workspace = true } memchr = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
uucore = { workspace = true, features = ["fs", "pipes"] } uucore = { workspace = true, features = ["fast-inc", "fs", "pipes"] }
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
nix = { workspace = true } nix = { workspace = true }

View file

@ -24,7 +24,7 @@ use thiserror::Error;
use uucore::display::Quotable; use uucore::display::Quotable;
use uucore::error::UResult; use uucore::error::UResult;
use uucore::fs::FileInformation; use uucore::fs::FileInformation;
use uucore::{format_usage, help_about, help_usage}; use uucore::{fast_inc::fast_inc_one, format_usage, help_about, help_usage};
/// Linux splice support /// Linux splice support
#[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(any(target_os = "linux", target_os = "android"))]
@ -35,59 +35,45 @@ const ABOUT: &str = help_about!("cat.md");
struct LineNumber { struct LineNumber {
buf: Vec<u8>, buf: Vec<u8>,
print_start: usize,
num_start: usize,
num_end: usize,
} }
// Logic to store a string for the line number. Manually incrementing the value // Logic to store a string for the line number. Manually incrementing the value
// represented in a buffer like this is significantly faster than storing // represented in a buffer like this is significantly faster than storing
// a `usize` and using the standard Rust formatting macros to format a `usize` // a `usize` and using the standard Rust formatting macros to format a `usize`
// to a string each time it's needed. // to a string each time it's needed.
// String is initialized to " 1\t" and incremented each time `increment` is // Buffer is initialized to " 1\t" and incremented each time `increment` is
// called. When the value overflows the range storable in the buffer, a b'1' is // called, using uucore's fast_inc function that operates on strings.
// prepended and the counting continues.
impl LineNumber { impl LineNumber {
fn new() -> Self { fn new() -> Self {
// 1024-digit long line number should be enough to run `cat` for the lifetime of the universe.
let size = 1024;
let mut buf = vec![b'0'; size];
let init_str = " 1\t";
let print_start = buf.len() - init_str.len();
let num_start = buf.len() - 2;
let num_end = buf.len() - 1;
buf[print_start..].copy_from_slice(init_str.as_bytes());
LineNumber { LineNumber {
// Initialize buf to b" 1\t" buf,
buf: Vec::from(b" 1\t"), print_start,
num_start,
num_end,
} }
} }
fn increment(&mut self) { fn increment(&mut self) {
// skip(1) to avoid the \t in the last byte. self.num_start = fast_inc_one(self.buf.as_mut_slice(), self.num_start, self.num_end);
for ascii_digit in self.buf.iter_mut().rev().skip(1) { self.print_start = self.print_start.min(self.num_start);
// Working from the least-significant digit, increment the number in the buffer.
// If we hit anything other than a b'9' we can break since the next digit is
// unaffected.
// Also note that if we hit a b' ', we can think of that as a 0 and increment to b'1'.
// If/else here is faster than match (as measured with some benchmarking Apr-2025),
// probably since we can prioritize most likely digits first.
if (b'0'..=b'8').contains(ascii_digit) {
*ascii_digit += 1;
break;
} else if b'9' == *ascii_digit {
*ascii_digit = b'0';
} else {
assert_eq!(*ascii_digit, b' ');
*ascii_digit = b'1';
break;
}
}
if self.buf[0] == b'0' {
// This implies we've overflowed. In this case the buffer will be
// [b'0', b'0', ..., b'0', b'\t'].
// For debugging, the following logic would assert that to be the case.
// assert_eq!(*self.buf.last().unwrap(), b'\t');
// for ascii_digit in self.buf.iter_mut().rev().skip(1) {
// assert_eq!(*ascii_digit, b'0');
// }
// All we need to do is prepend a b'1' and we're good.
self.buf.insert(0, b'1');
}
} }
fn write(&self, writer: &mut impl Write) -> io::Result<()> { fn write(&self, writer: &mut impl Write) -> io::Result<()> {
writer.write_all(&self.buf) writer.write_all(&self.buf[self.print_start..])
} }
} }
@ -804,21 +790,36 @@ mod tests {
#[test] #[test]
fn test_incrementing_string() { fn test_incrementing_string() {
let mut incrementing_string = super::LineNumber::new(); let mut incrementing_string = super::LineNumber::new();
assert_eq!(b" 1\t", incrementing_string.buf.as_slice()); assert_eq!(
b" 1\t",
&incrementing_string.buf[incrementing_string.print_start..]
);
incrementing_string.increment(); incrementing_string.increment();
assert_eq!(b" 2\t", incrementing_string.buf.as_slice()); assert_eq!(
b" 2\t",
&incrementing_string.buf[incrementing_string.print_start..]
);
// Run through to 100 // Run through to 100
for _ in 3..=100 { for _ in 3..=100 {
incrementing_string.increment(); incrementing_string.increment();
} }
assert_eq!(b" 100\t", incrementing_string.buf.as_slice()); assert_eq!(
b" 100\t",
&incrementing_string.buf[incrementing_string.print_start..]
);
// Run through until we overflow the original size. // Run through until we overflow the original size.
for _ in 101..=1_000_000 { for _ in 101..=1_000_000 {
incrementing_string.increment(); incrementing_string.increment();
} }
// Confirm that the buffer expands when we overflow the original size. // Confirm that the start position moves when we overflow the original size.
assert_eq!(b"1000000\t", incrementing_string.buf.as_slice()); assert_eq!(
b"1000000\t",
&incrementing_string.buf[incrementing_string.print_start..]
);
incrementing_string.increment(); incrementing_string.increment();
assert_eq!(b"1000001\t", incrementing_string.buf.as_slice()); assert_eq!(
b"1000001\t",
&incrementing_string.buf[incrementing_string.print_start..]
);
} }
} }