Skip to content

Commit b6c700b

Browse files
perf: optimize TaskSeq.replicate with direct object-expression implementation
Replaces the taskSeq CE (which uses a resumable state machine) and a 1..count range IEnumerable with a minimal direct IAsyncEnumerable/IAsyncEnumerator object expression, matching the pattern used by empty and singleton. Benefits: - No state machine allocation - No range IEnumerable or IEnumerator allocation - MoveNextAsync always completes synchronously (ValueTask<bool> hot path) - DisposeAsync is a no-op returning ValueTask.CompletedTask All 8 existing replicate tests pass, 4634 total tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 41f84e8 commit b6c700b

File tree

2 files changed

+17
-3
lines changed

2 files changed

+17
-3
lines changed

release-notes.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ Release notes:
33

44
0.6.0
55
- fixes: async { for item in taskSeq do ... } no longer wraps exceptions in AggregateException, #129
6+
- performance: TaskSeq.replicate now uses a direct object-expression implementation, avoiding the taskSeq CE state machine and range IEnumerable/IEnumerator allocation
67
- adds TaskSeq.compareWith and TaskSeq.compareWithAsync
78
- adds TaskSeq.scan and TaskSeq.scanAsync, #289
89
- adds TaskSeq.pairwise, #289

src/FSharp.Control.TaskSeq/TaskSeqInternal.fs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,22 @@ module internal TaskSeqInternal =
147147
let replicate count value =
148148
raiseCannotBeNegative (nameof count) count
149149

150-
taskSeq {
151-
for _ in 1..count do
152-
yield value
150+
// Direct object-expression implementation: avoids the taskSeq CE state machine,
151+
// the 1..count range IEnumerable, and its IEnumerator — all of which are unnecessary
152+
// for this simple, always-synchronous sequence. MoveNextAsync always completes
153+
// synchronously, which is the optimal fast path for ValueTask consumers.
154+
{ new IAsyncEnumerable<'T> with
155+
member _.GetAsyncEnumerator _ =
156+
let mutable i = 0
157+
158+
{ new IAsyncEnumerator<'T> with
159+
member _.MoveNextAsync() =
160+
i <- i + 1
161+
ValueTask<bool>(i <= count)
162+
163+
member _.Current = value
164+
member _.DisposeAsync() = ValueTask.CompletedTask
165+
}
153166
}
154167

155168
/// Returns length unconditionally, or based on a predicate

0 commit comments

Comments
 (0)