Skip to content

Commit 2b6cefc

Browse files
authored
Merge branch 'main' into daily-perf-improver/benchmarkdotnet-setup
2 parents f6a68e4 + f42f94b commit 2b6cefc

1 file changed

Lines changed: 44 additions & 15 deletions

File tree

src/FSharp.Control.AsyncSeq/AsyncSeq.fs

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,27 @@ type AsyncSeqOp<'T> () =
291291
[<AutoOpen>]
292292
module AsyncSeqOp =
293293

294+
// Optimized enumerator for unfoldAsync with reduced allocations
295+
[<Sealed>]
296+
type OptimizedUnfoldEnumerator<'S, 'T> (f:'S -> Async<('T * 'S) option>, init:'S) =
297+
let mutable currentState = init
298+
let mutable disposed = false
299+
300+
interface IAsyncEnumerator<'T> with
301+
member __.MoveNext () : Async<'T option> =
302+
if disposed then async.Return None
303+
else async {
304+
let! result = f currentState
305+
match result with
306+
| None ->
307+
return None
308+
| Some (value, nextState) ->
309+
currentState <- nextState
310+
return Some value
311+
}
312+
member __.Dispose () =
313+
disposed <- true
314+
294315
type UnfoldAsyncEnumerator<'S, 'T> (f:'S -> Async<('T * 'S) option>, init:'S) =
295316
inherit AsyncSeqOp<'T> ()
296317
override x.IterAsync g = async {
@@ -337,17 +358,7 @@ module AsyncSeqOp =
337358
new UnfoldAsyncEnumerator<'S, 'U> (h, init) :> _
338359
interface IAsyncEnumerable<'T> with
339360
member __.GetEnumerator () =
340-
let s = ref init
341-
{ new IAsyncEnumerator<'T> with
342-
member __.MoveNext () : Async<'T option> = async {
343-
let! next = f !s
344-
match next with
345-
| None ->
346-
return None
347-
| Some (a,s') ->
348-
s := s'
349-
return Some a }
350-
member __.Dispose () = () }
361+
new OptimizedUnfoldEnumerator<'S, 'T>(f, init) :> IAsyncEnumerator<'T>
351362

352363

353364

@@ -784,14 +795,32 @@ module AsyncSeq =
784795
// --------------------------------------------------------------------------
785796
// Additional combinators (implemented as async/asyncSeq computations)
786797

798+
// Optimized mapAsync enumerator that avoids computation builder overhead
799+
type private OptimizedMapAsyncEnumerator<'T, 'TResult>(source: IAsyncEnumerator<'T>, f: 'T -> Async<'TResult>) =
800+
let mutable disposed = false
801+
802+
interface IAsyncEnumerator<'TResult> with
803+
member _.MoveNext() = async {
804+
let! moveResult = source.MoveNext()
805+
match moveResult with
806+
| None -> return None
807+
| Some value ->
808+
let! mapped = f value
809+
return Some mapped
810+
}
811+
812+
member _.Dispose() =
813+
if not disposed then
814+
disposed <- true
815+
source.Dispose()
816+
787817
let mapAsync f (source : AsyncSeq<'T>) : AsyncSeq<'TResult> =
788818
match source with
789819
| :? AsyncSeqOp<'T> as source -> source.MapAsync f
790820
| _ ->
791-
asyncSeq {
792-
for itm in source do
793-
let! v = f itm
794-
yield v }
821+
{ new IAsyncEnumerable<'TResult> with
822+
member _.GetEnumerator() =
823+
new OptimizedMapAsyncEnumerator<'T, 'TResult>(source.GetEnumerator(), f) :> IAsyncEnumerator<'TResult> }
795824

796825
let mapiAsync f (source : AsyncSeq<'T>) : AsyncSeq<'TResult> = asyncSeq {
797826
let i = ref 0L

0 commit comments

Comments
 (0)