Skip to content

Commit dc17197

Browse files
committed
perf(core): stream a generator's output to the generators that read it
Every dependency edge was a full barrier: the cache collected all of a generator's chunks before its dependents started, so each stage of a build waited for the whole of the one before it. A generator can now set streamsInput: true to receive its dependency's output as an async iterable of chunks, read as they are produced. With one thread nothing runs alongside, so it still gets one array, and a dependency that doesn't stream arrives as a single chunk. Nothing sets it yet. The cache runs a streamed generator once, recording its chunks so any number of readers each read all of them, from the first, while it still runs. Once every reader has started, it lets go of a chunk as soon as all of them have read it, rather than holding the output until the last one is done. Assisted-by: Claude Opus 5.5 <noreply@anthropic.com> Assisted-by: Claude Sonnet 5.5 <noreply@anthropic.com>
1 parent e677946 commit dc17197

9 files changed

Lines changed: 985 additions & 41 deletions

File tree

‎.changeset/stream-input.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@doc-kit/core': minor
3+
---
4+
5+
Generators can start on their dependency's first chunk instead of waiting for all of it: with `streamsInput: true`, a generator receives its dependency's output as an async iterable of chunks (with one thread, still whole), which `worker.stream` accepts. The cache records a streamed generator once for all the generators reading it, each from the first chunk, and lets go of a chunk once every one of them has read it.

‎docs/creating-generators.md‎

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,9 +330,61 @@ export async function* generate(input, worker) {
330330
### Benefits of Streaming
331331

332332
- **Reduced memory usage** - Process data in chunks
333-
- **Earlier downstream starts** - Next generator can begin before this one finishes
333+
- **Earlier downstream starts** - A generator that streams its input (see below)
334+
begins on the first chunk, before this one finishes
334335
- **Better parallelism** - Multiple generators can work simultaneously
335336

337+
### Streaming Input
338+
339+
By default, a generator receives its dependency's output once it is complete,
340+
collected into one array. With `streamsInput: true`, it receives it as it is
341+
produced instead: `input` is an async iterable of the dependency's chunks, and
342+
if the dependency streams its results too, the generator starts as soon as the
343+
dependency does.
344+
345+
```mjs displayName="index.mjs"
346+
import { generate, processChunk } from './generate.mjs';
347+
348+
export default {
349+
name: 'per-chunk-generator',
350+
351+
description: 'Processes each chunk of metadata as it arrives',
352+
353+
dependsOn: '@doc-kit/core/metadata',
354+
355+
hasParallelProcessor: true,
356+
357+
streamsInput: true,
358+
359+
generate,
360+
processChunk,
361+
};
362+
```
363+
364+
`worker.stream` takes the chunks as they arrive, starting each one's work while
365+
the next ones are still being produced:
366+
367+
```mjs displayName="generate.mjs"
368+
export async function* generate(input, worker) {
369+
for await (const chunkResult of worker.stream(input, {})) {
370+
yield chunkResult;
371+
}
372+
}
373+
```
374+
375+
- A dependency that does not stream arrives as a single chunk, once it is
376+
complete.
377+
- With one thread, nothing would run alongside, so the input arrives whole, as
378+
it does without `streamsInput`: accept both, as `worker.stream` does.
379+
- With more than one thread, `input` can be iterated only once, and passing it
380+
to `worker.stream` iterates it. If you need it twice, collect it into an
381+
array first.
382+
- The chunks are the dependency's own, in the order it yields them.
383+
- Work that needs the whole input, such as sorting or writing one file, has to
384+
wait for the last chunk.
385+
- Other generators reading the same dependency are not affected: each gets
386+
every chunk, from the first.
387+
336388
### Non-Streaming Generators
337389

338390
Some generators must collect all input before processing.

0 commit comments

Comments
 (0)