diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 65c1d5e..7ec734e 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -2,6 +2,7 @@ * Test coverage: Added tests for previously-untested public API functions `AsyncSeq.tryFirst`, `AsyncSeq.firstOrDefault`, `AsyncSeq.zipWithParallel`, `AsyncSeq.combineLatestWithAsync`, and `AsyncSeq.toObservable`. 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) +* 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`. ### 4.17.0 diff --git a/src/FSharp.Control.AsyncSeq/AsyncSeq.fs b/src/FSharp.Control.AsyncSeq/AsyncSeq.fs index a6bb766..d50650d 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 }