Skip to content

Commit 53ee24c

Browse files
authored
Merge pull request #386 from fsprojects/repo-assist/perf-while-bang-groupby-countby-partition-20260413-6e4499650e6f9190
[Repo Assist] perf: use while! in groupBy, countBy, partition, except, exceptOfSeq
2 parents 6f25229 + 8a72218 commit 53ee24c

File tree

2 files changed

+24
-50
lines changed

2 files changed

+24
-50
lines changed

release-notes.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Release notes:
55
- adds TaskSeq.chooseV, TaskSeq.chooseVAsync, #385
66

77
1.0.0
8+
- perf: use while! in groupBy, countBy, partition, except, exceptOfSeq to eliminate redundant mutable 'go' variables and initial MoveNextAsync calls
89
- 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
910
- perf: toResizeArrayAsync (and therefore toArrayAsync, toListAsync, toResizeArrayAsync, toIListAsync) uses a direct loop instead of going through iter, avoiding a lambda and DU allocation per call
1011
- perf: tryItem uses a simpler loop that skips the redundant inner index check on every iteration

src/FSharp.Control.TaskSeq/TaskSeqInternal.fs

Lines changed: 23 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1484,35 +1484,29 @@ module internal TaskSeqInternal =
14841484

14851485
taskSeq {
14861486
use e = source.GetAsyncEnumerator CancellationToken.None
1487-
let mutable go = true
1488-
let! step = e.MoveNextAsync()
1489-
go <- step
1487+
let! hasFirst = e.MoveNextAsync()
14901488

1491-
if step then
1489+
if hasFirst then
14921490
// only create hashset by the time we actually start iterating;
14931491
// taskSeq enumerates sequentially, so a plain HashSet suffices — no locking needed.
14941492
let hashSet = HashSet<_>(HashIdentity.Structural)
14951493

14961494
use excl = itemsToExclude.GetAsyncEnumerator CancellationToken.None
1497-
let! exclStep = excl.MoveNextAsync()
1498-
let mutable exclGo = exclStep
14991495

1500-
while exclGo do
1496+
while! excl.MoveNextAsync() do
15011497
hashSet.Add excl.Current |> ignore
1502-
let! exclStep = excl.MoveNextAsync()
1503-
exclGo <- exclStep
15041498

1505-
while go do
1499+
// if true, it was added, and therefore unique, so we return it
1500+
// if false, it existed, and therefore a duplicate, and we skip
1501+
if hashSet.Add e.Current then
1502+
yield e.Current
1503+
1504+
while! e.MoveNextAsync() do
15061505
let current = e.Current
15071506

1508-
// if true, it was added, and therefore unique, so we return it
1509-
// if false, it existed, and therefore a duplicate, and we skip
15101507
if hashSet.Add current then
15111508
yield current
15121509

1513-
let! step = e.MoveNextAsync()
1514-
go <- step
1515-
15161510
}
15171511

15181512
let exceptOfSeq itemsToExclude (source: TaskSeq<_>) =
@@ -1521,26 +1515,24 @@ module internal TaskSeqInternal =
15211515

15221516
taskSeq {
15231517
use e = source.GetAsyncEnumerator CancellationToken.None
1524-
let mutable go = true
1525-
let! step = e.MoveNextAsync()
1526-
go <- step
1518+
let! hasFirst = e.MoveNextAsync()
15271519

1528-
if step then
1520+
if hasFirst then
15291521
// only create hashset by the time we actually start iterating;
15301522
// initialize directly from the seq — taskSeq is sequential so no locking needed.
15311523
let hashSet = HashSet<_>(itemsToExclude, HashIdentity.Structural)
15321524

1533-
while go do
1525+
// if true, it was added, and therefore unique, so we return it
1526+
// if false, it existed, and therefore a duplicate, and we skip
1527+
if hashSet.Add e.Current then
1528+
yield e.Current
1529+
1530+
while! e.MoveNextAsync() do
15341531
let current = e.Current
15351532

1536-
// if true, it was added, and therefore unique, so we return it
1537-
// if false, it existed, and therefore a duplicate, and we skip
15381533
if hashSet.Add current then
15391534
yield current
15401535

1541-
let! step = e.MoveNextAsync()
1542-
go <- step
1543-
15441536
}
15451537

15461538
let distinctUntilChanged (source: TaskSeq<_>) =
@@ -1623,12 +1615,10 @@ module internal TaskSeqInternal =
16231615
use e = source.GetAsyncEnumerator CancellationToken.None
16241616
let groups = Dictionary<'Key, ResizeArray<'T>>(HashIdentity.Structural)
16251617
let order = ResizeArray<'Key>()
1626-
let! step = e.MoveNextAsync()
1627-
let mutable go = step
16281618

16291619
match projector with
16301620
| ProjectorAction proj ->
1631-
while go do
1621+
while! e.MoveNextAsync() do
16321622
let key = proj e.Current
16331623
let mutable ra = Unchecked.defaultof<_>
16341624

@@ -1638,11 +1628,9 @@ module internal TaskSeqInternal =
16381628
order.Add key
16391629

16401630
ra.Add e.Current
1641-
let! step = e.MoveNextAsync()
1642-
go <- step
16431631

16441632
| AsyncProjectorAction proj ->
1645-
while go do
1633+
while! e.MoveNextAsync() do
16461634
let! key = proj e.Current
16471635
let mutable ra = Unchecked.defaultof<_>
16481636

@@ -1652,8 +1640,6 @@ module internal TaskSeqInternal =
16521640
order.Add key
16531641

16541642
ra.Add e.Current
1655-
let! step = e.MoveNextAsync()
1656-
go <- step
16571643

16581644
return
16591645
Array.init order.Count (fun i ->
@@ -1668,33 +1654,27 @@ module internal TaskSeqInternal =
16681654
use e = source.GetAsyncEnumerator CancellationToken.None
16691655
let counts = Dictionary<'Key, int>(HashIdentity.Structural)
16701656
let order = ResizeArray<'Key>()
1671-
let! step = e.MoveNextAsync()
1672-
let mutable go = step
16731657

16741658
match projector with
16751659
| ProjectorAction proj ->
1676-
while go do
1660+
while! e.MoveNextAsync() do
16771661
let key = proj e.Current
16781662
let mutable count = 0
16791663

16801664
if not (counts.TryGetValue(key, &count)) then
16811665
order.Add key
16821666

16831667
counts[key] <- count + 1
1684-
let! step = e.MoveNextAsync()
1685-
go <- step
16861668

16871669
| AsyncProjectorAction proj ->
1688-
while go do
1670+
while! e.MoveNextAsync() do
16891671
let! key = proj e.Current
16901672
let mutable count = 0
16911673

16921674
if not (counts.TryGetValue(key, &count)) then
16931675
order.Add key
16941676

16951677
counts[key] <- count + 1
1696-
let! step = e.MoveNextAsync()
1697-
go <- step
16981678

16991679
return Array.init order.Count (fun i -> let k = order[i] in k, counts[k])
17001680
}
@@ -1706,29 +1686,22 @@ module internal TaskSeqInternal =
17061686
use e = source.GetAsyncEnumerator CancellationToken.None
17071687
let trueItems = ResizeArray<'T>()
17081688
let falseItems = ResizeArray<'T>()
1709-
let! step = e.MoveNextAsync()
1710-
let mutable go = step
17111689

17121690
match predicate with
17131691
| Predicate pred ->
1714-
while go do
1692+
while! e.MoveNextAsync() do
17151693
let item = e.Current
17161694

17171695
if pred item then
17181696
trueItems.Add item
17191697
else
17201698
falseItems.Add item
17211699

1722-
let! step = e.MoveNextAsync()
1723-
go <- step
1724-
17251700
| PredicateAsync pred ->
1726-
while go do
1701+
while! e.MoveNextAsync() do
17271702
let item = e.Current
17281703
let! result = pred item
17291704
if result then trueItems.Add item else falseItems.Add item
1730-
let! step = e.MoveNextAsync()
1731-
go <- step
17321705

17331706
return trueItems.ToArray(), falseItems.ToArray()
17341707
}

0 commit comments

Comments
 (0)