Issue #423: Standard input as a full pipe pays the worker hand-off per 8 KiB chunk (ADR-263 §6: measure first)

Open and answer this issue on GitHub.

libraries, note · opened 2026-10-04 · updated 2026-10-06 · 1 comment

What it is about

ADR-263 D1 (#412) reads a regular file on the calling thread when the runtime is idle. Standard input stays on an I/O worker (ADR-263 §6). That holds up for a terminal or a slow peer: the read waits far longer for the input than the hand-off costs.

It does not hold for standard input as a pipe that is already full:

cat measurements.txt | prog

Here the bytes are there at once, as a file’s are. A program that walks the input a line at a time (io::lines, Part III 17.1) reads it in chunks through rt::io::stdin_chunk:

io::read_to_string / io::read (all of stdin at once) make a single hand-off and are not affected.

Work

  1. Measure first. Run a program that counts lines from a large piped stdin under callgrind, against the same program reading the same data from a file. Record the difference per chunk.
  2. Cheap parts before any decision:
    • Offer the buffer as capacity instead of zeroing it (as D3 did for the ring).
    • Check whether a larger CHUNK (e.g. 64 KiB) changes the count. The single read per chunk stays, so a slow pipe still yields whatever is there.
  3. Only if it still measures: a calling-thread path for stdin on an idle runtime, under ADR-263 D2’s condition. It would need to:
    • read only when the runtime is idle (D2), so blocking on a silent pipe harms nobody, since nothing else is waiting;
    • leave a terminal on the worker, or show that it does not matter there.

    This would be an amendment to ADR-263 §6, not a new setting (ADR-303 D5).

Not in scope

Sockets. They are non-blocking and read on readiness, and the hand-off is noise beside network latency (ADR-263 §6).

Comments

keywan-ghadami, 2026-10-04

Steps 1 and 2 are done in 0.0.442 (99f9961).

The measurement. I did not use callgrind here: under valgrind the threads run serialized, and every hand-off to the worker becomes a scheduler round, so the run did not finish in 20 minutes. Instead I took wall-clock times. A program that counts lines and their bytes through io::lines(), opt-level = 3, 4 M lines (60 MB, handwritten gen), best of 9, interleaved, 4 cores:

  cat data \| prog prog < data
8 KiB a hop, buffer zeroed (before) 791 ms 671 ms
8 KiB a hop, no zeroing (take(want).read_to_end into capacity, measurement only) 799 ms -
64 KiB a hop, zeroed (now) 414 ms 389 ms

Step 3, a calling-thread path for a full pipe, is not built. It amends ADR-263 §6, and whether it still pays at 64 KiB has to be measured against a file read of the same lines first. §6 says that much now.


Generated by Claude Code

Back to Open Issues