1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

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.
This commit is contained in:
Nicolas Boichat 2025-03-18 20:03:47 +01:00
parent c197a4203c
commit 6d3c0bee68
2 changed files with 35 additions and 4 deletions

View file

@ -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

View file

@ -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<num_format::Float>>,
) -> 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 {