Skip to content

seq: Buffer writes to stdout #7485

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion src/uu/seq/BENCHMARKING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
6 changes: 3 additions & 3 deletions src/uu/seq/src/seq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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 {
Expand Down
Loading