diff --git a/release-notes.txt b/release-notes.txt index d07e4fc..888256a 100644 --- a/release-notes.txt +++ b/release-notes.txt @@ -3,6 +3,7 @@ Release notes: 1.0.0 - adds taskSeqDynamic computation expression and TaskSeqDynamic/TaskSeqDynamicInfo types for dynamic (FSI-compatible) resumable code, fixing issue where taskSeq would raise NotImplementedException in F# Interactive, #246 + - perf: simplify iter, fold, reduce, mapFold, tryLast, skipOrTake (Drop/Truncate) to use while! and remove manual go-flag and initial MoveNextAsync pre-advance, matching the pattern already used by sum/sumBy/average - perf: TaskSeq.chunkBy and chunkByAsync reuse the ResizeArray buffer between chunks, reducing allocations on sequences with many chunk boundaries - fixes: TaskSeq.insertAt, insertManyAt, removeAt, removeManyAt, updateAt now raise ArgumentNullException (not NullReferenceException) when given a null source; insertManyAt also validates the values argument - refactor: simplify lengthBy and lengthBeforeMax to use while! and remove the redundant mutable 'go' and initial MoveNextAsync diff --git a/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs b/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs index 261dbba..44de771 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs @@ -357,43 +357,31 @@ module internal TaskSeqInternal = task { use e = source.GetAsyncEnumerator CancellationToken.None - let mutable go = true - let! step = e.MoveNextAsync() - go <- step - // this ensures that the inner loop is optimized for the closure - // though perhaps we need to split into individual functions after all to use - // InlineIfLambda? + // Each branch keeps its own while! loop so the match dispatch is hoisted out and + // the JIT sees a tight, single-case loop (same pattern as sum/sumBy etc.). match action with | CountableAction action -> let mutable i = 0 - while go do - do action i e.Current - let! step = e.MoveNextAsync() + while! e.MoveNextAsync() do + action i e.Current i <- i + 1 - go <- step | SimpleAction action -> - while go do - do action e.Current - let! step = e.MoveNextAsync() - go <- step + while! e.MoveNextAsync() do + action e.Current | AsyncCountableAction action -> let mutable i = 0 - while go do + while! e.MoveNextAsync() do do! action i e.Current - let! step = e.MoveNextAsync() i <- i + 1 - go <- step | AsyncSimpleAction action -> - while go do + while! e.MoveNextAsync() do do! action e.Current - let! step = e.MoveNextAsync() - go <- step } let fold folder initial (source: TaskSeq<_>) = @@ -401,24 +389,17 @@ module internal TaskSeqInternal = task { use e = source.GetAsyncEnumerator CancellationToken.None - let mutable go = true let mutable result = initial - let! step = e.MoveNextAsync() - go <- step match folder with | FolderAction folder -> - while go do + while! e.MoveNextAsync() do result <- folder result e.Current - let! step = e.MoveNextAsync() - go <- step | AsyncFolderAction folder -> - while go do + while! e.MoveNextAsync() do let! tempResult = folder result e.Current result <- tempResult - let! step = e.MoveNextAsync() - go <- step return result } @@ -457,22 +438,16 @@ module internal TaskSeqInternal = raiseEmptySeq () let mutable result = e.Current - let! step = e.MoveNextAsync() - let mutable go = step match folder with | FolderAction folder -> - while go do + while! e.MoveNextAsync() do result <- folder result e.Current - let! step = e.MoveNextAsync() - go <- step | AsyncFolderAction folder -> - while go do + while! e.MoveNextAsync() do let! tempResult = folder result e.Current result <- tempResult - let! step = e.MoveNextAsync() - go <- step return result } @@ -482,28 +457,21 @@ module internal TaskSeqInternal = task { use e = source.GetAsyncEnumerator CancellationToken.None - let mutable go = true let mutable state = initial let results = ResizeArray() - let! step = e.MoveNextAsync() - go <- step match folder with | MapFolderAction folder -> - while go do + while! e.MoveNextAsync() do let result, newState = folder state e.Current results.Add result state <- newState - let! step = e.MoveNextAsync() - go <- step | AsyncMapFolderAction folder -> - while go do + while! e.MoveNextAsync() do let! (result, newState) = folder state e.Current results.Add result state <- newState - let! step = e.MoveNextAsync() - go <- step return results.ToArray(), state } @@ -804,15 +772,10 @@ module internal TaskSeqInternal = task { use e = source.GetAsyncEnumerator CancellationToken.None - let mutable go = true let mutable last = ValueNone - let! step = e.MoveNextAsync() - go <- step - while go do + while! e.MoveNextAsync() do last <- ValueSome e.Current - let! step = e.MoveNextAsync() - go <- step match last with | ValueSome value -> return Some value @@ -1233,24 +1196,19 @@ module internal TaskSeqInternal = else taskSeq { use e = source.GetAsyncEnumerator CancellationToken.None + let mutable i = 0 + let mutable cont = true - let! step = e.MoveNextAsync() - let mutable cont = step - let mutable pos = 0 - - // skip, or stop looping if we reached the end - while cont do - pos <- pos + 1 - - if pos < count then - let! moveNext = e.MoveNextAsync() - cont <- moveNext - else - cont <- false + // advance past 'count' elements; stop early if the source is shorter + while cont && i < count do + let! hasMore = e.MoveNextAsync() + if hasMore then i <- i + 1 else cont <- false - // return the rest - while! e.MoveNextAsync() do - yield e.Current + // return remaining elements; enumerator is at element (count-1) so one + // more MoveNext is needed to reach element (count) + if cont then + while! e.MoveNextAsync() do + yield e.Current } | Take -> @@ -1277,19 +1235,16 @@ module internal TaskSeqInternal = else taskSeq { use e = source.GetAsyncEnumerator CancellationToken.None + let mutable yielded = 0 + let mutable cont = true - let! step = e.MoveNextAsync() - let mutable cont = step - let mutable pos = 0 - - // return items until we've exhausted the seq - while cont do - yield e.Current - pos <- pos + 1 + // yield up to 'count' elements; stop when exhausted or limit reached + while cont && yielded < count do + let! hasMore = e.MoveNextAsync() - if pos < count then - let! moveNext = e.MoveNextAsync() - cont <- moveNext + if hasMore then + yield e.Current + yielded <- yielded + 1 else cont <- false