Skip to content

Commit 6b32e8c

Browse files
authored
Eagerly perform set insertions in nub{Ord,Int} (#1229)
This results in an improvement in overall performance. See the added Note [Eager set insertions] for an explanation. Also mark the go functions as strict in the input set. Benchmarks on GHC 9.14 show a decrease in allocations of 8% on nubOrd.noFusion_distinct and nubOrd.strings_noFusion_distinct.
1 parent 4af9d12 commit 6b32e8c

1 file changed

Lines changed: 29 additions & 11 deletions

File tree

containers/src/Data/Containers/ListUtils.hs

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,11 @@ nubOrdOn f = -- Inline with 1 arg
8181
nubOrdOnExcluding :: Ord b => (a -> b) -> Set b -> [a] -> [a]
8282
nubOrdOnExcluding f = go
8383
where
84-
go _ [] = []
85-
go s (x:xs) = case tryInsertSet fx s of
84+
go !_ [] = []
85+
go !s (x:xs) = case tryInsertSet fx s of
8686
Nothing -> go s xs
87-
Just s' -> x : go s' xs
87+
Just !s' -> -- See Note [Eager set insertions]
88+
x : go s' xs
8889
where !fx = f x
8990

9091
tryInsertSet :: Ord a => a -> Set a -> Maybe (Set a)
@@ -114,15 +115,16 @@ nubOrdOnFB :: Ord b
114115
-> Set b
115116
-> r
116117
nubOrdOnFB f c = -- Inline with 2 args
117-
\x r -> oneShot (\s ->
118+
\x r -> oneShot (\ !s ->
118119
let !y = f x
119120
in case tryInsertSet y s of
120121
Nothing -> r s
121-
Just s' -> x `c` r s')
122+
Just !s' -> -- See Note [Eager set insertions]
123+
x `c` r s')
122124
{-# INLINE [0] nubOrdOnFB #-}
123125

124126
constNubOn :: a -> b -> a
125-
constNubOn x _ = x
127+
constNubOn x !_ = x
126128
{-# INLINE [0] constNubOn #-}
127129
#endif
128130

@@ -167,10 +169,12 @@ nubIntOn f = -- Inline with 1 arg
167169
nubIntOnExcluding :: (a -> Int) -> IntSet -> [a] -> [a]
168170
nubIntOnExcluding f = go
169171
where
170-
go _ [] = []
171-
go s (x:xs)
172+
go !_ [] = []
173+
go !s (x:xs)
172174
| fx `IntSet.member` s = go s xs
173-
| otherwise = x : go (IntSet.insert fx s) xs
175+
| otherwise =
176+
let !s' = IntSet.insert fx s -- See Note [Eager set insertions]
177+
in x : go s' xs
174178
where !fx = f x
175179

176180
#ifdef __GLASGOW_HASKELL__
@@ -194,10 +198,24 @@ nubIntOnFB :: (a -> Int)
194198
-> IntSet
195199
-> r
196200
nubIntOnFB f c = -- Inline with 2 args
197-
\x r -> oneShot (\s ->
201+
\x r -> oneShot (\ !s ->
198202
let !y = f x
199203
in if y `IntSet.member` s
200204
then r s
201-
else x `c` r (IntSet.insert y s))
205+
else let !s' = IntSet.insert y s -- See Note [Eager set insertions]
206+
in x `c` r s')
202207
{-# INLINE [0] nubIntOnFB #-}
203208
#endif
209+
210+
-- Note [Eager set insertions]
211+
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~
212+
--
213+
-- In nubOrd and nubInt we insert new elements into the set eagerly. This means
214+
-- that we perform a bit of work before we yield the current element which is
215+
-- not strictly necessary.
216+
--
217+
-- The lazier option would be to create a thunk for the new set, which would get
218+
-- forced by the membership check in the next step. However, a thunk has a small
219+
-- overhead, and the small costs of thunks at every step adds up to a noticeable
220+
-- amount of time and allocations overall. So, we avoid this and perform the
221+
-- insertions eagerly instead.

0 commit comments

Comments
 (0)