@@ -24,7 +24,7 @@ use thiserror::Error;
2424use uucore:: display:: Quotable ;
2525use uucore:: error:: UResult ;
2626use 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" ) ) ]
@@ -33,61 +33,55 @@ mod splice;
3333const USAGE : & str = help_usage ! ( "cat.md" ) ;
3434const ABOUT : & str = help_about ! ( "cat.md" ) ;
3535
36+ // Allocate 32 digits for the line number.
37+ // An estimate is that we can print about 1e8 lines/seconds, so 32 digits
38+ // would be enough for billions of universe lifetimes.
39+ const LINE_NUMBER_BUF_SIZE : usize = 32 ;
40+
3641struct LineNumber {
37- buf : Vec < u8 > ,
42+ buf : [ u8 ; LINE_NUMBER_BUF_SIZE ] ,
43+ print_start : usize ,
44+ num_start : usize ,
45+ num_end : usize ,
3846}
3947
4048// Logic to store a string for the line number. Manually incrementing the value
4149// represented in a buffer like this is significantly faster than storing
4250// a `usize` and using the standard Rust formatting macros to format a `usize`
4351// 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.
52+ // Buffer is initialized to " 1\t" and incremented each time `increment` is
53+ // called, using uucore's fast_inc function that operates on strings.
4754impl LineNumber {
4855 fn new ( ) -> Self {
56+ let mut buf = [ b'0' ; LINE_NUMBER_BUF_SIZE ] ;
57+
58+ let init_str = " 1\t " ;
59+ let print_start = buf. len ( ) - init_str. len ( ) ;
60+ let num_start = buf. len ( ) - 2 ;
61+ let num_end = buf. len ( ) - 1 ;
62+
63+ buf[ print_start..] . copy_from_slice ( init_str. as_bytes ( ) ) ;
64+
4965 LineNumber {
50- // Initialize buf to b" 1\t"
51- buf : Vec :: from ( b" 1\t " ) ,
66+ buf,
67+ print_start,
68+ num_start,
69+ num_end,
5270 }
5371 }
5472
5573 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- }
74+ fast_inc_one ( & mut self . buf , & mut self . num_start , self . num_end ) ;
75+ self . print_start = self . print_start . min ( self . num_start ) ;
76+ }
77+
78+ #[ inline]
79+ fn to_str ( & self ) -> & [ u8 ] {
80+ & self . buf [ self . print_start ..]
8781 }
8882
8983 fn write ( & self , writer : & mut impl Write ) -> io:: Result < ( ) > {
90- writer. write_all ( & self . buf )
84+ writer. write_all ( self . to_str ( ) )
9185 }
9286}
9387
@@ -804,21 +798,21 @@ mod tests {
804798 #[ test]
805799 fn test_incrementing_string ( ) {
806800 let mut incrementing_string = super :: LineNumber :: new ( ) ;
807- assert_eq ! ( b" 1\t " , incrementing_string. buf . as_slice ( ) ) ;
801+ assert_eq ! ( b" 1\t " , incrementing_string. to_str ( ) ) ;
808802 incrementing_string. increment ( ) ;
809- assert_eq ! ( b" 2\t " , incrementing_string. buf . as_slice ( ) ) ;
803+ assert_eq ! ( b" 2\t " , incrementing_string. to_str ( ) ) ;
810804 // Run through to 100
811805 for _ in 3 ..=100 {
812806 incrementing_string. increment ( ) ;
813807 }
814- assert_eq ! ( b" 100\t " , incrementing_string. buf . as_slice ( ) ) ;
808+ assert_eq ! ( b" 100\t " , incrementing_string. to_str ( ) ) ;
815809 // Run through until we overflow the original size.
816810 for _ in 101 ..=1_000_000 {
817811 incrementing_string. increment ( ) ;
818812 }
819- // Confirm that the buffer expands when we overflow the original size.
820- assert_eq ! ( b"1000000\t " , incrementing_string. buf . as_slice ( ) ) ;
813+ // Confirm that the start position moves when we overflow the original size.
814+ assert_eq ! ( b"1000000\t " , incrementing_string. to_str ( ) ) ;
821815 incrementing_string. increment ( ) ;
822- assert_eq ! ( b"1000001\t " , incrementing_string. buf . as_slice ( ) ) ;
816+ assert_eq ! ( b"1000001\t " , incrementing_string. to_str ( ) ) ;
823817 }
824818}
0 commit comments