Skip to content

[Repo Assist] Perf: optimise mapiAsync with direct enumerator#287

Merged
dsyme merged 3 commits intomainfrom
repo-assist/perf-mapiasync-2026-03-23-18b8b82a487eb386
Mar 25, 2026
Merged

[Repo Assist] Perf: optimise mapiAsync with direct enumerator#287
dsyme merged 3 commits intomainfrom
repo-assist/perf-mapiasync-2026-03-23-18b8b82a487eb386

Conversation

@github-actions
Copy link
Copy Markdown
Contributor

🤖 This PR was created by Repo Assist, an automated AI assistant.

Summary

Replaces the asyncSeq-builder + collect implementation of mapiAsync with a new OptimizedMapiAsyncEnumerator type that iterates the source directly and maintains the index in a mutable field.

Before (asyncSeq builder path):

let mapiAsync f (source : AsyncSeq<'T>) : AsyncSeq<'TResult> = asyncSeq {
    let i = ref 0L
    for itm in source do    // → calls collect / asyncSeq.For overhead
      let! v = f i.Value itm
      i := i.Value + 1L
      yield v }

After (direct enumerator, same pattern as mapAsync):

type private OptimizedMapiAsyncEnumerator<'T, 'TResult>(...) =
    let mutable index = 0L
    interface IAsyncSeqEnumerator<'TResult> with
      member _.MoveNext() = async {
        let! moveResult = source.MoveNext()
        match moveResult with
        | None -> return None
        | Some value ->
            let i = index
            index <- index + 1L
            let! mapped = f i value
            return Some mapped }

Why

mapiAsync is a core transformation used by mapi and indexed. The previous implementation went through asyncSeq.Forcollect, which allocates a generator per element. The new implementation is a thin enumerator wrapper with no per-element allocation overhead, matching the approach already used by mapAsync, filterAsync, and chooseAsync.

Benchmarks added

New AsyncSeqMapiBenchmarks class in the benchmarks project measures mapAsync (baseline), mapiAsync, and mapi at 1K and 10K elements.

Test Status

✅ Build succeeded (netstandard2.0, warnings only — pre-existing).
✅ All 321/321 tests passed.

Generated by Repo Assist ·

To install this agentic workflow, run

gh aw add githubnext/agentics/workflows/repo-assist.md@346204513ecfa08b81566450d7d599556807389f

Generated by Repo Assist ·

To install this agentic workflow, run

gh aw add githubnext/agentics/workflows/repo-assist.md@d1d884596e62351dd652ae78465885dd32f0dd7d

Replace the asyncSeq-builder + collect implementation of mapiAsync with
a new OptimizedMapiAsyncEnumerator that iterates the source directly and
maintains the index in a mutable field.  This mirrors the existing
OptimizedMapAsyncEnumerator and eliminates the collect/For overhead that
was previously incurred for every element.

Also adds AsyncSeqMapiBenchmarks to measure mapAsync vs mapiAsync vs mapi.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dsyme dsyme marked this pull request as ready for review March 23, 2026 17:01
@dsyme dsyme merged commit 78bf31a into main Mar 25, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant