perf(nl): optimize line numbering by using itoa and direct writing (#10757)

This commit is contained in:
Rostyslav Toch
2026-02-06 20:09:40 +00:00
committed by GitHub
parent 25a43c636f
commit ffe2b0b0a5
3 changed files with 64 additions and 27 deletions
Generated
+1
View File
@@ -3797,6 +3797,7 @@ dependencies = [
"clap",
"codspeed-divan-compat",
"fluent",
"itoa",
"regex",
"tempfile",
"uucore",
+1
View File
@@ -19,6 +19,7 @@ path = "src/nl.rs"
[dependencies]
clap = { workspace = true }
itoa = { workspace = true }
regex = { workspace = true }
uucore = { workspace = true }
fluent = { workspace = true }
+62 -27
View File
@@ -6,7 +6,7 @@
use clap::{Arg, ArgAction, Command};
use std::ffi::{OsStr, OsString};
use std::fs::File;
use std::io::{BufRead, BufReader, BufWriter, Read, Write, stdin, stdout};
use std::io::{self, BufRead, BufReader, BufWriter, Read, Write, stdin, stdout};
use std::path::Path;
use uucore::display::Quotable;
use uucore::error::{FromIo, UResult, USimpleError, set_exit_code};
@@ -122,13 +122,41 @@ impl<T: AsRef<str>> From<T> for NumberFormat {
impl NumberFormat {
/// Turns a line number into a `String` with at least `min_width` chars,
/// formatted according to the `NumberFormat`s variant.
fn format(&self, number: i64, min_width: usize) -> String {
fn format_to<W: Write>(&self, writer: &mut W, number: i64, min_width: usize) -> io::Result<()> {
let mut buffer = itoa::Buffer::new();
match self {
Self::Left => format!("{number:<min_width$}"),
Self::Right => format!("{number:>min_width$}"),
Self::RightZero if number < 0 => format!("-{0:0>1$}", number.abs(), min_width - 1),
Self::RightZero => format!("{number:0>min_width$}"),
Self::Left => {
let num = buffer.format(number);
writer.write_all(num.as_bytes())?;
for _ in num.len()..min_width {
writer.write_all(b" ")?;
}
}
Self::Right => {
let num = buffer.format(number);
for _ in num.len()..min_width {
writer.write_all(b" ")?;
}
writer.write_all(num.as_bytes())?;
}
Self::RightZero if number < 0 => {
writer.write_all(b"-")?;
let num = buffer.format(number.abs());
for _ in num.len()..min_width.saturating_sub(1) {
writer.write_all(b"0")?;
}
writer.write_all(num.as_bytes())?;
}
Self::RightZero => {
let num = buffer.format(number);
for _ in num.len()..min_width {
writer.write_all(b"0")?;
}
writer.write_all(num.as_bytes())?;
}
}
Ok(())
}
}
@@ -346,8 +374,7 @@ pub fn uu_app() -> Command {
}
/// Helper to write: prefix bytes + line bytes + newline
fn write_line(writer: &mut impl Write, prefix: &[u8], line: &[u8]) -> std::io::Result<()> {
writer.write_all(prefix)?;
fn write_line(writer: &mut impl Write, line: &[u8]) -> io::Result<()> {
writer.write_all(line)?;
writeln!(writer)
}
@@ -416,19 +443,22 @@ fn nl<T: Read>(reader: &mut BufReader<T>, stats: &mut Stats, settings: &Settings
translate!("nl-error-line-number-overflow"),
));
};
let mut prefix = settings
settings
.number_format
.format(line_number, settings.number_width)
.into_bytes();
prefix.extend_from_slice(settings.number_separator.as_encoded_bytes());
write_line(&mut writer, &prefix, &line)
.format_to(&mut writer, line_number, settings.number_width)
.map_err_context(|| translate!("nl-error-could-not-write"))?;
writer
.write_all(settings.number_separator.as_encoded_bytes())
.map_err_context(|| translate!("nl-error-could-not-write"))?;
stats.line_number = line_number.checked_add(settings.line_increment);
} else {
let prefix = " ".repeat(settings.number_width + 1);
write_line(&mut writer, prefix.as_bytes(), &line)
writer
.write_all(prefix.as_bytes())
.map_err_context(|| translate!("nl-error-could-not-write"))?;
}
write_line(&mut writer, &line)
.map_err_context(|| translate!("nl-error-could-not-write"))?;
}
}
writer
@@ -442,21 +472,26 @@ mod test {
use super::*;
#[test]
#[allow(clippy::cognitive_complexity)]
fn test_format() {
assert_eq!(NumberFormat::Left.format(12, 1), "12");
assert_eq!(NumberFormat::Left.format(-12, 1), "-12");
assert_eq!(NumberFormat::Left.format(12, 4), "12 ");
assert_eq!(NumberFormat::Left.format(-12, 4), "-12 ");
let helper = |fmt: NumberFormat, num: i64, width: usize| -> String {
let mut buf = Vec::new();
fmt.format_to(&mut buf, num, width).unwrap();
String::from_utf8(buf).unwrap()
};
assert_eq!(NumberFormat::Right.format(12, 1), "12");
assert_eq!(NumberFormat::Right.format(-12, 1), "-12");
assert_eq!(NumberFormat::Right.format(12, 4), " 12");
assert_eq!(NumberFormat::Right.format(-12, 4), " -12");
assert_eq!(helper(NumberFormat::Left, 12, 1), "12");
assert_eq!(helper(NumberFormat::Left, -12, 1), "-12");
assert_eq!(helper(NumberFormat::Left, 12, 4), "12 ");
assert_eq!(helper(NumberFormat::Left, -12, 4), "-12 ");
assert_eq!(NumberFormat::RightZero.format(12, 1), "12");
assert_eq!(NumberFormat::RightZero.format(-12, 1), "-12");
assert_eq!(NumberFormat::RightZero.format(12, 4), "0012");
assert_eq!(NumberFormat::RightZero.format(-12, 4), "-012");
assert_eq!(helper(NumberFormat::Right, 12, 1), "12");
assert_eq!(helper(NumberFormat::Right, -12, 1), "-12");
assert_eq!(helper(NumberFormat::Right, 12, 4), " 12");
assert_eq!(helper(NumberFormat::Right, -12, 4), " -12");
assert_eq!(helper(NumberFormat::RightZero, 12, 1), "12");
assert_eq!(helper(NumberFormat::RightZero, -12, 1), "-12");
assert_eq!(helper(NumberFormat::RightZero, 12, 4), "0012");
assert_eq!(helper(NumberFormat::RightZero, -12, 4), "-012");
}
}