Skip to content

Commit c9f8844

Browse files
Repo AssistCopilot
authored andcommitted
perf: remove unnecessary Lazy allocation in TaskSeq.init per iteration
The `init` and `initInfinite` functions previously wrapped each yielded value in a `Lazy<'T>` object on every iteration, then immediately discarded it. This caused one heap allocation per element with no benefit: - `Lazy<_>.Create(fun () -> init i)` allocates a Lazy + closure - `value.Force()` goes through the Lazy lock-check machinery - `value <- Unchecked.defaultof<_>` immediately nulls it out The original rationale was thread safety for concurrent enumerator access, but IAsyncEnumerator<T> explicitly does not support concurrent use, and the pattern did not actually protect against all concurrent access bugs. Replace with direct calls: `yield init i` and `let! result = asyncInit i; yield result`. Also includes prerequisite fix: bare range expressions `{ 1..10 }` in TaskSeq.Concat.Tests.fs replaced with list literals `[1..10]` to fix FS3873 deprecation error on newer F# compilers (>= 9.x / .NET SDK 10.x). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 0fccf7b commit c9f8844

File tree

2 files changed

+5
-21
lines changed

2 files changed

+5
-21
lines changed

src/FSharp.Control.TaskSeq.Test/TaskSeq.Concat.Tests.fs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,8 +249,8 @@ module SideEffect =
249249
let mutable i = 0
250250

251251
taskSeq {
252-
yield ResizeArray { 1..10 }
253-
yield ResizeArray { 1..10 }
252+
yield ResizeArray [1..10]
253+
yield ResizeArray [1..10]
254254

255255
yield
256256
ResizeArray(

src/FSharp.Control.TaskSeq/TaskSeqInternal.fs

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,6 @@ module internal TaskSeqInternal =
277277

278278
let init count initializer = taskSeq {
279279
let mutable i = 0
280-
let mutable value: Lazy<'T> = Unchecked.defaultof<_>
281280

282281
let count =
283282
match count with
@@ -290,28 +289,13 @@ module internal TaskSeqInternal =
290289
match initializer with
291290
| InitAction init ->
292291
while i < count do
293-
// using Lazy gives us locking and safe multiple access to the cached value, if
294-
// multiple threads access the same item through the same enumerator (which is
295-
// bad practice, but hey, who're we to judge).
296-
if isNull value then
297-
value <- Lazy<_>.Create(fun () -> init i)
298-
299-
yield value.Force()
300-
value <- Unchecked.defaultof<_>
292+
yield init i
301293
i <- i + 1
302294

303295
| InitActionAsync asyncInit ->
304296
while i < count do
305-
// using Lazy gives us locking and safe multiple access to the cached value, if
306-
// multiple threads access the same item through the same enumerator (which is
307-
// bad practice, but hey, who're we to judge).
308-
if isNull value then
309-
// TODO: is there a 'Lazy' we can use with Task?
310-
let! value' = asyncInit i
311-
value <- Lazy<_>.CreateFromValue value'
312-
313-
yield value.Force()
314-
value <- Unchecked.defaultof<_>
297+
let! result = asyncInit i
298+
yield result
315299
i <- i + 1
316300

317301
}

0 commit comments

Comments
 (0)