Skip to content

Commit 7978074

Browse files
authored
Merge pull request #197 from fsprojects/daily-perf-improver/optimize-collect-operation
Daily Perf Improver: Optimize collect operation for better performance
2 parents e6f7782 + f1c5784 commit 7978074

File tree

1 file changed

+55
-42
lines changed

1 file changed

+55
-42
lines changed

src/FSharp.Control.AsyncSeq/AsyncSeq.fs

Lines changed: 55 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -591,49 +591,62 @@ module AsyncSeq =
591591
| HaveInnerEnumerator of IAsyncEnumerator<'T> * IAsyncEnumerator<'U>
592592
| Finished
593593

594+
// Optimized collect implementation using direct field access instead of ref cells
595+
type OptimizedCollectEnumerator<'T, 'U>(f: 'T -> AsyncSeq<'U>, inp: AsyncSeq<'T>) =
596+
// Mutable fields instead of ref cells to reduce allocations
597+
let mutable inputEnumerator: IAsyncEnumerator<'T> option = None
598+
let mutable innerEnumerator: IAsyncEnumerator<'U> option = None
599+
let mutable disposed = false
600+
601+
// Tail-recursive optimization to avoid deep continuation chains
602+
let rec moveNextLoop () : Async<'U option> = async {
603+
if disposed then return None
604+
else
605+
match innerEnumerator with
606+
| Some inner ->
607+
let! result = inner.MoveNext()
608+
match result with
609+
| Some value -> return Some value
610+
| None ->
611+
inner.Dispose()
612+
innerEnumerator <- None
613+
return! moveNextLoop ()
614+
| None ->
615+
match inputEnumerator with
616+
| Some outer ->
617+
let! result = outer.MoveNext()
618+
match result with
619+
| Some value ->
620+
let newInner = (f value).GetEnumerator()
621+
innerEnumerator <- Some newInner
622+
return! moveNextLoop ()
623+
| None ->
624+
outer.Dispose()
625+
inputEnumerator <- None
626+
disposed <- true
627+
return None
628+
| None ->
629+
let newOuter = inp.GetEnumerator()
630+
inputEnumerator <- Some newOuter
631+
return! moveNextLoop ()
632+
}
633+
634+
interface IAsyncEnumerator<'U> with
635+
member _.MoveNext() = moveNextLoop ()
636+
member _.Dispose() =
637+
if not disposed then
638+
disposed <- true
639+
match innerEnumerator with
640+
| Some inner -> inner.Dispose(); innerEnumerator <- None
641+
| None -> ()
642+
match inputEnumerator with
643+
| Some outer -> outer.Dispose(); inputEnumerator <- None
644+
| None -> ()
645+
594646
let collect (f: 'T -> AsyncSeq<'U>) (inp: AsyncSeq<'T>) : AsyncSeq<'U> =
595-
{ new IAsyncEnumerable<'U> with
596-
member x.GetEnumerator() =
597-
let state = ref (CollectState.NotStarted inp)
598-
{ new IAsyncEnumerator<'U> with
599-
member x.MoveNext() =
600-
async { match !state with
601-
| CollectState.NotStarted inp ->
602-
return!
603-
(let e1 = inp.GetEnumerator()
604-
state := CollectState.HaveInputEnumerator e1
605-
x.MoveNext())
606-
| CollectState.HaveInputEnumerator e1 ->
607-
let! res1 = e1.MoveNext()
608-
return!
609-
(match res1 with
610-
| Some v1 ->
611-
let e2 = (f v1).GetEnumerator()
612-
state := CollectState.HaveInnerEnumerator (e1, e2)
613-
| None ->
614-
x.Dispose()
615-
x.MoveNext())
616-
| CollectState.HaveInnerEnumerator (e1, e2) ->
617-
let! res2 = e2.MoveNext()
618-
match res2 with
619-
| None ->
620-
state := CollectState.HaveInputEnumerator e1
621-
dispose e2
622-
return! x.MoveNext()
623-
| Some _ ->
624-
return res2
625-
| _ ->
626-
return None }
627-
member x.Dispose() =
628-
match !state with
629-
| CollectState.HaveInputEnumerator e1 ->
630-
state := CollectState.Finished
631-
dispose e1
632-
| CollectState.HaveInnerEnumerator (e1, e2) ->
633-
state := CollectState.Finished
634-
dispose e2
635-
dispose e1
636-
| _ -> () } }
647+
{ new IAsyncEnumerable<'U> with
648+
member _.GetEnumerator() =
649+
new OptimizedCollectEnumerator<'T, 'U>(f, inp) :> IAsyncEnumerator<'U> }
637650

638651
// let collect (f: 'T -> AsyncSeq<'U>) (inp: AsyncSeq<'T>) : AsyncSeq<'U> =
639652
// AsyncGenerator.collect f inp

0 commit comments

Comments
 (0)