Skip to content

Commit 4af9d12

Browse files
authored
Use Set.alterF in nubOrd (#1228)
Compared to the previous implementation of `member` followed by `insert`, `alterF` uses `memberIndex` and `insertAt`. This avoids all the element comparisons in `insert`, at the cost of `memberIndex` being a little more expensive than `member`. I expect this change to be an improvement in typical use cases.
1 parent f480dae commit 4af9d12

3 files changed

Lines changed: 26 additions & 12 deletions

File tree

containers-tests/benchmarks/ListUtils.hs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,26 @@ main :: IO ()
1010
main = do
1111
evaluate $ rnf [xs_distinct, xs_repeat]
1212
evaluate $ rnf [xss_distinct, xss_repeat]
13+
evaluate $ rnf [strings_distinct, strings_repeat]
1314
defaultMain
1415
[ bgroup "nubOrd"
15-
[ bench "no_fusion_distinct" $
16+
[ bench "noFusion_distinct" $
1617
whnf (consumeNoFusion . LU.nubOrd) xs_distinct
17-
, bench "no_fusion_repeat" $
18+
, bench "noFusion_repeat" $
1819
whnf (consumeNoFusion . LU.nubOrd) xs_repeat
1920
, bench "issue1202_distinct" $
2021
whnf (consumeNoFusion . collectFrameworksDirs) xss_distinct
2122
, bench "issue1202_repeat" $
2223
whnf (consumeNoFusion . collectFrameworksDirs) xss_repeat
24+
, bench "strings_noFusion_distinct" $
25+
whnf (consumeNoFusion . LU.nubOrd) strings_distinct
26+
, bench "strings_noFusion_repeat" $
27+
whnf (consumeNoFusion . LU.nubOrd) strings_repeat
2328
]
2429
, bgroup "nubInt"
25-
[ bench "no_fusion_distinct" $
30+
[ bench "noFusion_distinct" $
2631
whnf (consumeNoFusion . LU.nubInt) xs_distinct
27-
, bench "no_fusion_repeat" $
32+
, bench "noFusion_repeat" $
2833
whnf (consumeNoFusion . LU.nubInt) xs_repeat
2934
, bench "issue1202_distinct" $
3035
whnf (consumeNoFusion . collectFrameworksDirs_nubInt) xss_distinct
@@ -33,12 +38,17 @@ main = do
3338
]
3439
]
3540
where
36-
bound = 1000 :: Int
41+
!bound = 1000 :: Int
42+
3743
xs_distinct = [1..bound]
3844
xs_repeat = replicate bound 1 :: [Int]
3945
xss_distinct = [[i] | i <- [1..bound]]
4046
xss_repeat = replicate bound [1] :: [[Int]]
4147

48+
-- Use strings as an example of expensive Ord comparisons.
49+
strings_distinct = map show xs_distinct
50+
strings_repeat = map show xs_repeat
51+
4252
-- Simple version of the case reported in
4353
-- https://github.com/haskell/containers/issues/1202
4454
collectFrameworksDirs :: [[Int]] -> [Int]

containers-tests/containers-tests.cabal

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ benchmark set-operations-set
286286
build-depends:
287287
benchmark-utils
288288

289-
benchmark listutils
289+
benchmark listutils-benchmarks
290290
import: benchmark-deps, warnings
291291
default-language: Haskell2010
292292
type: exitcode-stdio-1.0

containers/src/Data/Containers/ListUtils.hs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,15 @@ nubOrdOnExcluding :: Ord b => (a -> b) -> Set b -> [a] -> [a]
8282
nubOrdOnExcluding f = go
8383
where
8484
go _ [] = []
85-
go s (x:xs)
86-
| fx `Set.member` s = go s xs
87-
| otherwise = x : go (Set.insert fx s) xs
85+
go s (x:xs) = case tryInsertSet fx s of
86+
Nothing -> go s xs
87+
Just s' -> x : go s' xs
8888
where !fx = f x
8989

90+
tryInsertSet :: Ord a => a -> Set a -> Maybe (Set a)
91+
tryInsertSet = Set.alterF (\found -> if found then Nothing else Just True)
92+
{-# INLINE tryInsertSet #-}
93+
9094
#ifdef __GLASGOW_HASKELL__
9195
-- We want this inlinable to specialize to the necessary Ord instance.
9296
{-# INLINABLE [1] nubOrdOnExcluding #-}
@@ -112,9 +116,9 @@ nubOrdOnFB :: Ord b
112116
nubOrdOnFB f c = -- Inline with 2 args
113117
\x r -> oneShot (\s ->
114118
let !y = f x
115-
in if y `Set.member` s
116-
then r s
117-
else x `c` r (Set.insert y s))
119+
in case tryInsertSet y s of
120+
Nothing -> r s
121+
Just s' -> x `c` r s')
118122
{-# INLINE [0] nubOrdOnFB #-}
119123

120124
constNubOn :: a -> b -> a

0 commit comments

Comments
 (0)