Skip to content

Perf: bring IAsyncEnumerable processors onto the worker-pool model #337

Description

@thomhurst

Problem

PR #332 moved the array-based processors to a fixed worker pool, but the IAsyncEnumerable processors and extensions still use the older per-item machinery (findings from the perf audit):

  1. AsyncEnumerableParallelProcessor / ResultAsyncEnumerableParallelProcessor (rate-limited paths): per item, one closure + one Task.Run task + a semaphore wait — ~300-400 B and two scheduler hops each.
  2. ResultAsyncEnumerableParallelProcessor: head-drain via List.RemoveAt(0) — O(N²) reference shifts at scale; a Queue<Task<TOutput>> is O(1) and only the head is ever inspected.
  3. AsyncEnumerableExtensions.ProcessInParallel no-selector overloads: pay Task.Run + semaphore per item to do nothing — it is a ToListAsync costing 10-100x what it should; a plain await foreach collect suffices.
  4. Task.Run(async () => await f(x)) double state machine in 3 sites — Task.Run(() => f(x)) unwraps identically.

Proposal

  • Streaming rate-limited paths: P persistent workers reading a bounded Channel<TInput> (same shape as WorkerPool, adapted to streaming) — preserves the order-preserving yield semantics of the result processor.
  • Queue for the head-drain.
  • Direct collect for no-selector overloads; drop redundant async lambdas.

Acceptance criteria

  • Streaming behaviour (ordering, cancellation, backpressure) covered by tests before refactoring.
  • Benchmark comparison in the benchmarks project (see benchmarks issue).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions