From 6d3c0bee68dbcb02ef343f1505b33cfdc2c80246 Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Tue, 18 Mar 2025 20:03:47 +0100 Subject: [PATCH] seq: Buffer writes to stdout Use a BufWriter to wrap stdout: reduces the numbers of system calls, improves performance drastically (2x in some cases). Also document use cases in src/uu/seq/BENCHMARKING.md, and the optimization we have just done here. --- src/uu/seq/BENCHMARKING.md | 33 ++++++++++++++++++++++++++++++++- src/uu/seq/src/seq.rs | 6 +++--- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/uu/seq/BENCHMARKING.md b/src/uu/seq/BENCHMARKING.md index a633d509c..89cc47861 100644 --- a/src/uu/seq/BENCHMARKING.md +++ b/src/uu/seq/BENCHMARKING.md @@ -19,7 +19,38 @@ Finally, you can compare the performance of the two versions of `seq` by running, for example, ```shell -hyperfine "seq 1000000" "target/release/seq 1000000" +hyperfine -L seq seq,target/release/seq "{seq} 1000000" ``` +## Interesting test cases + +Performance characteristics may vary a lot depending on the parameters, +and if custom formatting is required. In particular, it does appear +that the GNU implementation is heavily optimized for positive integer +outputs (which is probably the most common use case for `seq`). + +Specifying a format or fixed width will slow down the +execution a lot (~15-20 times on GNU `seq`): +```shell +hyperfine -L seq seq,target/release/seq "{seq} -f%g 1000000" +hyperfine -L seq seq,target/release/seq "{seq} -w 1000000" +``` + +Floating point increments, or any negative bound, also degrades the +performance (~10-15 times on GNU `seq`): +```shell +hyperfine -L seq seq,./target/release/seq "{seq} 0 0.000001 1" +hyperfine -L seq seq,./target/release/seq "{seq} -100 1 1000000" +``` + +## Optimizations + +### Buffering stdout + +The original `uutils` implementation of `seq` did unbuffered writes +to stdout, causing a large number of system calls (and therefore a large amount +of system time). Simply wrapping `stdout` in a `BufWriter` increased performance +by about 2 times for a floating point increment test case, leading to similar +performance compared with GNU `seq`. + [0]: https://github.com/sharkdp/hyperfine diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index a6b5e32ea..08b989815 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -4,7 +4,7 @@ // file that was distributed with this source code. // spell-checker:ignore (ToDO) bigdecimal extendedbigdecimal numberparse hexadecimalfloat use std::ffi::OsString; -use std::io::{stdout, ErrorKind, Write}; +use std::io::{stdout, BufWriter, ErrorKind, Write}; use clap::{Arg, ArgAction, Command}; use num_traits::{ToPrimitive, Zero}; @@ -262,8 +262,8 @@ fn print_seq( padding: usize, format: Option<&Format>, ) -> std::io::Result<()> { - let stdout = stdout(); - let mut stdout = stdout.lock(); + let stdout = stdout().lock(); + let mut stdout = BufWriter::new(stdout); let (first, increment, last) = range; let mut value = first; let padding = if pad {