[Toybox] [PATCH] tr: fix pathological flushing.

Rob Landley rob at landley.net
Sat Dec 5 08:21:57 PST 2020


On 12/5/20 8:55 AM, Josh Gao wrote:
> On Sat, Dec 5, 2020, 3:38 AM Rob Landley <rob at landley.net
> <mailto:rob at landley.net>> wrote:
> 
>     (Also, line buffering sucks because it'll flush at the buffer size anyway so
>     you're not guaranteed to get a full line of contiguous output. What it REALLY
>     wants is nagle's algorithm for stdout but no libc ever bothered to IMPLEMENT it,
>     possibly because of the runtime expense... Ahem. My point is commands should
>     probably do sane output blocking on their own.)
> 
> 
> AFAIK, the only way this would work is if libc only ever does nonblocking
> writes to stdout,

I was thinking "it's been 1/10 of a second since pending data went into the
buffer and it hasn't gotten written out yet, perform the blocking write() now".
Which could be threads, or alarm() and have the signal handler do the flush
(which could go wrong in a half-dozen different ways and would have been way
easier to bake into the semantics in 1985 than 2020). But the expense of setting
and resetting signal handlers every time you start a write probably makes it a
net loss, because it's still extra system calls.

Mostly I was complaining that the semantics of the effect they _want_ is
available in the other context but not this one, and what they're actually doing
doesn't accomplish it. If it was easy to accomplish those semantics efficiently
in my own plumbing I'd have tried it already. :P

> which also means it would need to spawn a thread or use
> SIGIO to flush its buffer when stdout becomes available, plus modify the
> flags on STDOUT_FILENO to be O_NONBLOCK (which doesn't even work on regular
> files). I think people would be far more annoyed with this behavior than any
> potential gains would justify?

In my original message, "possibly because of the runtime expense" was a
load-bearing phrase. :)

> (io_uring might make things more interesting, though? You could eliminate
> libc's buffering entirely, and just memcpy and submit a write for every single
> fwrite, up to some buffer limit.)

If we had an output mechanism that worked like linux-vdso instead of syscalls,
we could do all sorts of fun stuff, but I don't really kernel much these days
unless it's directly job-related:

  http://lkml.iu.edu/hypermail/linux/kernel/2011.1/06282.html

They're no fun anymore.

The fundamental problem here is that single byte write() is an order of
magnitude slower than page size write(), which as far as I can tell is why FILE
* exists. (That and ungetc().) And any solution that involves another system
call isn't going to be an improvement. :(

But this has been true forever. At linuxworld expo Jon "maddog" Hall had a talk
about how back in the big iron days he sped up a reel-to-reel tape backup (and
made it fit on one tape instead of a dozen) by replacing single byte writes with
block writes. (In that case the tape spun down and spun back up, leaving blank
tape between each write and putting start and end sequences around each chunk of
data, which is why that was a pathological case. Nagel tries to avoid 1 byte per
ethernet frame which is why THAT is a pathological case. And here it's system
call overhead.

The two hardest problems in computer science remain cache invalidation, naming
things, and off by one errors.

Rob



More information about the Toybox mailing list