Skip to content

Commit 606655e

Browse files
committed
cat: Use fast_inc
Gets within 1-2% of original implementation.
1 parent f176716 commit 606655e

File tree

1 file changed

+77
-34
lines changed

1 file changed

+77
-34
lines changed

src/uu/cat/src/cat.rs

Lines changed: 77 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,68 @@ mod splice;
3333
const USAGE: &str = help_usage!("cat.md");
3434
const ABOUT: &str = help_about!("cat.md");
3535

36+
/// Fast code path increment function.
37+
///
38+
/// Add inc to the string val[start..end]. This operates on ASCII digits, assuming
39+
/// val and inc are well formed.
40+
///
41+
/// Returns the new value for start (can be less that the original value if we
42+
/// have a carry or if inc > start).
43+
///
44+
/// We also assume that there is enough space in val to expand if start needs
45+
/// to be updated.
46+
#[inline]
47+
fn fast_inc(val: &mut [u8], start: usize, end: usize, inc: &[u8]) -> usize {
48+
// To avoid a lot of casts to signed integers, we make sure to decrement pos
49+
// as late as possible, so that it does not ever go negative.
50+
let mut pos = end;
51+
let mut carry = 0u8;
52+
53+
// First loop, add all digits of inc into val.
54+
for inc_pos in (0..inc.len()).rev() {
55+
pos -= 1;
56+
57+
let mut new_val = inc[inc_pos] + carry;
58+
// Be careful here, only add existing digit of val.
59+
if pos >= start {
60+
new_val += val[pos] - b'0';
61+
}
62+
if new_val > b'9' {
63+
carry = 1;
64+
new_val -= 10;
65+
} else {
66+
carry = 0;
67+
}
68+
val[pos] = new_val;
69+
}
70+
71+
// Done, now, if we have a carry, add that to the upper digits of val.
72+
if carry == 0 {
73+
return start.min(pos);
74+
}
75+
while pos > start {
76+
pos -= 1;
77+
78+
if val[pos] == b'9' {
79+
// 9+1 = 10. Carry propagating, keep going.
80+
val[pos] = b'0';
81+
} else {
82+
// Carry stopped propagating, return unchanged start.
83+
val[pos] += 1;
84+
return start;
85+
}
86+
}
87+
88+
// The carry propagated so far that a new digit was added.
89+
val[start - 1] = b'1';
90+
start - 1
91+
}
92+
3693
struct LineNumber {
3794
buf: Vec<u8>,
95+
print_start: usize,
96+
start: usize,
97+
num_end: usize,
3898
}
3999

40100
// Logic to store a string for the line number. Manually incrementing the value
@@ -46,48 +106,31 @@ struct LineNumber {
46106
// prepended and the counting continues.
47107
impl LineNumber {
48108
fn new() -> Self {
109+
let size = 1024;
110+
let mut buf = vec![b'0'; size];
111+
112+
let init_str = " 1\t";
113+
let print_start = buf.len() - init_str.len();
114+
let start = buf.len() - 2;
115+
let num_end = buf.len() - 1;
116+
117+
buf[print_start..].copy_from_slice(init_str.as_bytes());
118+
49119
LineNumber {
50-
// Initialize buf to b" 1\t"
51-
buf: Vec::from(b" 1\t"),
120+
buf,
121+
print_start,
122+
start,
123+
num_end,
52124
}
53125
}
54126

55127
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-
}
128+
self.start = fast_inc(self.buf.as_mut_slice(), self.start, self.num_end, &[b'1']);
129+
self.print_start = self.print_start.min(self.start);
87130
}
88131

89132
fn write(&self, writer: &mut impl Write) -> std::io::Result<()> {
90-
writer.write_all(&self.buf)
133+
writer.write_all(&self.buf[self.print_start..])
91134
}
92135
}
93136

0 commit comments

Comments
 (0)