Skip to content

Commit e939eda

Browse files
authored
numfmt: optimize output handling by using stdout directly - improve performance by 4.47% (#11051)
* numfmt: optimize output handling by using stdout directly * refactor(numfmt): replace stdout usage with writer parameter
1 parent 38ba366 commit e939eda

File tree

2 files changed

+57
-40
lines changed

2 files changed

+57
-40
lines changed

src/uu/numfmt/src/format.rs

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
//
33
// For the full copyright and license information, please view the LICENSE
44
// file that was distributed with this source code.
5+
56
// spell-checker:ignore powf
7+
68
use uucore::display::Quotable;
79
use uucore::translate;
810

@@ -467,21 +469,19 @@ fn split_bytes<'a>(input: &'a [u8], delim: &'a [u8]) -> impl Iterator<Item = &'a
467469
})
468470
}
469471

470-
pub fn format_and_print_delimited(input: &[u8], options: &NumfmtOptions) -> Result<()> {
471-
let delimiter = options.delimiter.as_ref().unwrap();
472-
let mut output: Vec<u8> = Vec::new();
473-
let eol = if options.zero_terminated {
474-
b'\0'
475-
} else {
476-
b'\n'
477-
};
472+
pub fn write_formatted_with_delimiter<W: std::io::Write>(
473+
writer: &mut W,
474+
input: &[u8],
475+
options: &NumfmtOptions,
476+
) -> Result<()> {
477+
let delimiter = options.delimiter.as_deref().unwrap();
478478

479479
for (n, field) in (1..).zip(split_bytes(input, delimiter)) {
480480
let field_selected = uucore::ranges::contain(&options.fields, n);
481481

482482
// add delimiter before second and subsequent fields
483483
if n > 1 {
484-
output.extend_from_slice(delimiter);
484+
writer.write_all(delimiter).unwrap();
485485
}
486486

487487
if field_selected {
@@ -490,21 +490,28 @@ pub fn format_and_print_delimited(input: &[u8], options: &NumfmtOptions) -> Resu
490490
.map_err(|_| translate!("numfmt-error-invalid-number", "input" => String::from_utf8_lossy(field).into_owned().quote()))?
491491
.trim_start();
492492
let formatted = format_string(field_str, options, None)?;
493-
output.extend_from_slice(formatted.as_bytes());
493+
writer.write_all(formatted.as_bytes()).unwrap();
494494
} else {
495495
// add unselected field without conversion
496-
output.extend_from_slice(field);
496+
writer.write_all(field).unwrap();
497497
}
498498
}
499499

500-
output.push(eol);
501-
std::io::Write::write_all(&mut std::io::stdout(), &output).map_err(|e| e.to_string())?;
500+
let eol = if options.zero_terminated {
501+
b"\0"
502+
} else {
503+
b"\n"
504+
};
505+
writer.write_all(eol).unwrap();
502506

503507
Ok(())
504508
}
505-
pub fn format_and_print_whitespace(s: &str, options: &NumfmtOptions) -> Result<()> {
506-
let mut output = String::new();
507509

510+
pub fn write_formatted_with_whitespace<W: std::io::Write>(
511+
writer: &mut W,
512+
s: &str,
513+
options: &NumfmtOptions,
514+
) -> Result<()> {
508515
for (n, (prefix, field)) in (1..).zip(WhitespaceSplitter { s: Some(s) }) {
509516
let field_selected = uucore::ranges::contain(&options.fields, n);
510517

@@ -513,7 +520,7 @@ pub fn format_and_print_whitespace(s: &str, options: &NumfmtOptions) -> Result<(
513520

514521
// add delimiter before second and subsequent fields
515522
let prefix = if n > 1 {
516-
output.push(' ');
523+
writer.write_all(b" ").unwrap();
517524
&prefix[1..]
518525
} else {
519526
prefix
@@ -525,24 +532,28 @@ pub fn format_and_print_whitespace(s: &str, options: &NumfmtOptions) -> Result<(
525532
None
526533
};
527534

528-
output.push_str(&format_string(field, options, implicit_padding)?);
535+
let formatted = format_string(field, options, implicit_padding)?;
536+
writer.write_all(formatted.as_bytes()).unwrap();
529537
} else {
530538
// the -z option converts an initial \n into a space
531539
let prefix = if options.zero_terminated && prefix.starts_with('\n') {
532-
output.push(' ');
540+
writer.write_all(b" ").unwrap();
533541
&prefix[1..]
534542
} else {
535543
prefix
536544
};
537545
// add unselected field without conversion
538-
output.push_str(prefix);
539-
output.push_str(field);
546+
writer.write_all(prefix.as_bytes()).unwrap();
547+
writer.write_all(field.as_bytes()).unwrap();
540548
}
541549
}
542550

543-
let eol = if options.zero_terminated { '\0' } else { '\n' };
544-
output.push(eol);
545-
print!("{output}");
551+
let eol = if options.zero_terminated {
552+
b"\0"
553+
} else {
554+
b"\n"
555+
};
556+
writer.write_all(eol).unwrap();
546557

547558
Ok(())
548559
}

src/uu/numfmt/src/numfmt.rs

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
// file that was distributed with this source code.
55

66
use crate::errors::NumfmtError;
7-
use crate::format::{format_and_print_delimited, format_and_print_whitespace};
7+
use crate::format::{write_formatted_with_delimiter, write_formatted_with_whitespace};
88
use crate::options::{
99
DEBUG, DELIMITER, FIELD, FIELD_DEFAULT, FORMAT, FROM, FROM_DEFAULT, FROM_UNIT,
1010
FROM_UNIT_DEFAULT, FormatOptions, HEADER, HEADER_DEFAULT, INVALID, InvalidModes, NUMBER,
@@ -14,7 +14,7 @@ use crate::options::{
1414
use crate::units::{Result, Unit};
1515
use clap::{Arg, ArgAction, ArgMatches, Command, builder::ValueParser, parser::ValueSource};
1616
use std::ffi::OsString;
17-
use std::io::{BufRead, Error, Write, stderr};
17+
use std::io::{BufRead, Error, Write as _, stderr};
1818
use std::result::Result as StdResult;
1919
use std::str::FromStr;
2020

@@ -32,8 +32,9 @@ pub mod options;
3232
mod units;
3333

3434
fn handle_args<'a>(args: impl Iterator<Item = &'a [u8]>, options: &NumfmtOptions) -> UResult<()> {
35+
let mut stdout = std::io::stdout().lock();
3536
for l in args {
36-
format_and_handle_validation(l, options)?;
37+
write_line(&mut stdout, l, options)?;
3738
}
3839
Ok(())
3940
}
@@ -51,33 +52,32 @@ fn handle_buffer_iterator(
5152
options: &NumfmtOptions,
5253
terminator: u8,
5354
) -> UResult<()> {
55+
let mut stdout = std::io::stdout().lock();
5456
for (idx, line_result) in iter.enumerate() {
5557
match line_result {
5658
Ok(line) if idx < options.header => {
57-
std::io::stdout().write_all(&line)?;
58-
std::io::stdout().write_all(&[terminator])?;
59+
stdout.write_all(&line)?;
60+
stdout.write_all(&[terminator])?;
5961
Ok(())
6062
}
61-
Ok(line) => format_and_handle_validation(&line, options),
63+
Ok(line) => write_line(&mut stdout, &line, options),
6264
Err(err) => return Err(Box::new(NumfmtError::IoError(err.to_string()))),
6365
}?;
6466
}
6567
Ok(())
6668
}
6769

68-
fn format_and_handle_validation(input_line: &[u8], options: &NumfmtOptions) -> UResult<()> {
69-
let eol = if options.zero_terminated {
70-
b'\0'
71-
} else {
72-
b'\n'
73-
};
74-
70+
fn write_line<W: std::io::Write>(
71+
writer: &mut W,
72+
input_line: &[u8],
73+
options: &NumfmtOptions,
74+
) -> UResult<()> {
7575
let handled_line = if options.delimiter.is_some() {
76-
format_and_print_delimited(input_line, options)
76+
write_formatted_with_delimiter(writer, input_line, options)
7777
} else {
7878
// Whitespace mode requires valid UTF-8
7979
match std::str::from_utf8(input_line) {
80-
Ok(s) => format_and_print_whitespace(s, options),
80+
Ok(s) => write_formatted_with_whitespace(writer, s, options),
8181
Err(_) => Err(translate!("numfmt-error-invalid-input")),
8282
}
8383
};
@@ -95,8 +95,14 @@ fn format_and_handle_validation(input_line: &[u8], options: &NumfmtOptions) -> U
9595
}
9696
InvalidModes::Ignore => {}
9797
}
98-
std::io::stdout().write_all(input_line)?;
99-
std::io::stdout().write_all(&[eol])?;
98+
writer.write_all(input_line)?;
99+
100+
let eol = if options.zero_terminated {
101+
b"\0"
102+
} else {
103+
b"\n"
104+
};
105+
writer.write_all(eol)?;
100106
}
101107

102108
Ok(())

0 commit comments

Comments
 (0)