diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 7597f4b..ac06869 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -5,6 +5,7 @@ * Test coverage: Added tests for `AsyncSeq.bufferByCount` (obsolete alias of `chunkBySize`) and `Seq.ofAsyncSeq`, which previously had no dedicated test coverage. No functional changes. * Fixed Fable CI build: `Microsoft.Bcl.AsyncInterfaces` was pinned to a specific version (`10.0.7`) that was older than the version resolved transitively via `System.Threading.Channels`, causing a `NU1605` package downgrade error that made Fable's project cracker fail during `dotnet fable`. The reference now uses `Version="*"` (matching `System.Threading.Channels`) so both resolve consistently. (#334) * Tests: Added comprehensive tests for `AsyncSeq.zapp`, `AsyncSeq.zappAsync`, and `AsyncSeq.compareWithAsync`, which previously had no dedicated test coverage. +* Performance: Optimised `AsyncSeq.distinctUntilChangedWithAsync` (and thus `distinctUntilChangedWith` / `distinctUntilChanged`) to track the previous element with a `hasPrev` flag and a direct `mutable` field instead of wrapping it in a `'T option`. Previously each iteration heap-allocated a new `Some` box; the new implementation eliminates that allocation, matching the pattern already used by `pairwise`. * Performance: Replaced the last remaining `ref` cell in `AsyncSeq.mergeAll`'s internal counter with a direct `mutable` local, matching the `ref`-to-`mutable` modernisation already applied elsewhere in the codebase. Eliminates a per-call heap allocation of a `Ref` wrapper; no behavioral change. ### 4.17.0 diff --git a/src/FSharp.Control.AsyncSeq/AsyncSeq.fs b/src/FSharp.Control.AsyncSeq/AsyncSeq.fs index 46cc7ff..6a514ce 100644 --- a/src/FSharp.Control.AsyncSeq/AsyncSeq.fs +++ b/src/FSharp.Control.AsyncSeq/AsyncSeq.fs @@ -2740,16 +2740,18 @@ module AsyncSeq = use ie = source.GetEnumerator() let! move = ie.MoveNext() let mutable b = move - let mutable prev = None + // Use a flag + mutable field instead of Option to avoid per-element heap allocation + let mutable hasPrev = false + let mutable prev = Unchecked.defaultof<'T> while b.IsSome do let v = b.Value - match prev with - | None -> + if not hasPrev then yield v - | Some p -> - let! changed = f p v + else + let! changed = f prev v if not changed then yield v - prev <- Some v + hasPrev <- true + prev <- v let! moven = ie.MoveNext() b <- moven } diff --git a/tests/FSharp.Control.AsyncSeq.Tests/AsyncSeqTests.fs b/tests/FSharp.Control.AsyncSeq.Tests/AsyncSeqTests.fs index ffc140e..39c4569 100644 --- a/tests/FSharp.Control.AsyncSeq.Tests/AsyncSeqTests.fs +++ b/tests/FSharp.Control.AsyncSeq.Tests/AsyncSeqTests.fs @@ -5258,6 +5258,7 @@ let ``Seq.ofAsyncSeq consumes elements lazily`` () = let result = AsyncSeq.ofSeq [1;2;3] |> Seq.ofAsyncSeq |> Seq.take 2 |> Seq.toList Assert.AreEqual([1;2], result) #endif + // ===== zapp / zappAsync ===== []