Skip to content

[Repo Assist] perf: direct loop in toResizeArrayAsync; simplify tryItem loop#381

Draft
github-actions[bot] wants to merge 1 commit intomainfrom
repo-assist/perf-toResizeArray-direct-loop-20260408-116ad7cb53aa926e
Draft

[Repo Assist] perf: direct loop in toResizeArrayAsync; simplify tryItem loop#381
github-actions[bot] wants to merge 1 commit intomainfrom
repo-assist/perf-toResizeArray-direct-loop-20260408-116ad7cb53aa926e

Conversation

@github-actions
Copy link
Copy Markdown
Contributor

@github-actions github-actions bot commented Apr 8, 2026

🤖 This is an automated pull request from Repo Assist, an AI assistant.

Summary

Two small, focused performance improvements to TaskSeqInternal.fs.

1. toResizeArrayAsync — direct loop instead of iter

Before:

let toResizeArrayAsync source =
    checkNonNull (nameof source) source
    task {
        let res = ResizeArray()
        do! source |> iter (SimpleAction(fun item -> res.Add item))
        return res
    }

After:

let toResizeArrayAsync (source: TaskSeq<'T>) =
    checkNonNull (nameof source) source
    task {
        let res = ResizeArray<'T>()
        use e = source.GetAsyncEnumerator CancellationToken.None
        while! e.MoveNextAsync() do
            res.Add e.Current
        return res
    }

Why it's faster: the old code created a lambda closure (fun item -> res.Add item), wrapped it in a SimpleAction discriminated-union case (a heap allocation), then called iter which pattern-matched on the DU and drove a manual go-flag loop. The new code does none of that.

Scope: toResizeArrayAsync is the implementation shared by toArrayAsync, toListAsync, toResizeArrayAsync, and toIListAsync, so all four terminal operations benefit.


2. tryItem — remove redundant inner index check

Before:

while go && idx <= index do
    if idx = index then
        foundItem <- Some e.Current
        go <- false
    else
        let! step = e.MoveNextAsync()
        go <- step
        idx <- idx + 1

After:

// advance past the first `index` elements, then capture the current element
while go && idx < index do
    let! step = e.MoveNextAsync()
    go <- step
    idx <- idx + 1

if go then
    foundItem <- Some e.Current

Why it's faster: the old loop condition idx <= index meant the if idx = index branch ran on every iteration (not just the last one). The refactored version moves the capture outside the loop, so there's no per-iteration inner comparison.


Trade-offs

  • Both changes are purely internal — no public API or behaviour change.
  • The toResizeArrayAsync change also removes the dependency on iter for this use-case, which improves code clarity.

Test Status

Build: 0 warnings, 0 errors (Release)
Fantomas: formatting check passes
Tests: 5093 passed, 2 skipped, 0 failed

Generated by 🌈 Repo Assist, see workflow run. Learn more.

Generated by 🌈 Repo Assist, see workflow run. Learn more.

To install this agentic workflow, run

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

toResizeArrayAsync previously went through the iter function with a
SimpleAction DU wrapper, causing a lambda closure allocation and
discriminated-union wrapping on every call. The new direct loop
eliminates these allocations and removes an extra layer of indirection.
This benefits toArrayAsync, toListAsync, toResizeArrayAsync, and
toIListAsync which all route through toResizeArrayAsync.

tryItem previously used a while-loop with condition idx <= index and an
inner if idx = index check on every iteration. The refactored version
advances with idx < index and captures the current element after the
loop, removing the redundant inner comparison from the hot path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

0 participants