Skip to content

Commit fbafcf7

Browse files
authored
Faster IntSet and IntMap split functions (#879)
* Add property test for IntMap.splitLookup * Add split, splitMember, splitLookup benchmarks * Use bin instead of union In IntMap and Intset split, splitLookup, splitMember * Move a few things around for clarity, no effect on performance * Fix missed strictness in IntSet splitMember
1 parent df1123d commit fbafcf7

File tree

5 files changed

+71
-40
lines changed

5 files changed

+71
-40
lines changed

containers-tests/benchmarks/IntMap.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ main = do
5151
, bench "minView" $ whnf (maybe 0 (\((k,v), m) -> k+v+M.size m) . M.minViewWithKey)
5252
(M.fromList $ zip [1..10] [1..10])
5353
, bench "spanAntitone" $ whnf (M.spanAntitone (<key_mid)) m
54+
, bench "split" $ whnf (M.split key_mid) m
55+
, bench "splitLookup" $ whnf (M.splitLookup key_mid) m
5456
]
5557
where
5658
elems = elems_hits

containers-tests/benchmarks/IntSet.hs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,18 @@ main = do
5050
$ whnf (num_transitions . det 2 0) $ hard_nfa 1111 16
5151
, bench "spanAntitone:dense" $ whnf (IS.spanAntitone (<elem_mid)) s
5252
, bench "spanAntitone:sparse" $ whnf (IS.spanAntitone (<elem_sparse_mid)) s_sparse
53+
, bench "split:dense" $ whnf (IS.split elem_mid) s
54+
, bench "split:sparse" $ whnf (IS.split elem_sparse_mid) s_sparse
55+
, bench "splitMember:dense" $ whnf (IS.splitMember elem_mid) s
56+
, bench "splitMember:sparse" $ whnf (IS.splitMember elem_sparse_mid) s_sparse
5357
]
5458
where
5559
elems = [1..2^12]
5660
elems_even = [2,4..2^12]
5761
elems_odd = [1,3..2^12]
58-
elem_mid = 2^11
62+
elem_mid = 2^11 + 31 -- falls in the middle of a packed Tip bitmask (assuming 64-bit words)
5963
elems_sparse = map (*64) elems -- when built into a map, each Tip is a singleton
60-
elem_sparse_mid = 64 * elem_mid
64+
elem_sparse_mid = 2^11 * 64
6165

6266
member :: [Int] -> IS.IntSet -> Int
6367
member xs s = foldl' (\n x -> if IS.member x s then n + 1 else n) 0 xs

containers-tests/tests/intmap-properties.hs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ main = defaultMain $ testGroup "intmap-properties"
187187
, testProperty "fmap" prop_fmap
188188
, testProperty "mapkeys" prop_mapkeys
189189
, testProperty "split" prop_splitModel
190+
, testProperty "splitLookup" prop_splitLookup
190191
, testProperty "splitRoot" prop_splitRoot
191192
, testProperty "foldr" prop_foldr
192193
, testProperty "foldr'" prop_foldr'
@@ -1519,6 +1520,16 @@ prop_splitModel n ys = length ys > 0 ==>
15191520
toAscList l === sort [(k, v) | (k,v) <- xs, k < n] .&&.
15201521
toAscList r === sort [(k, v) | (k,v) <- xs, k > n]
15211522

1523+
prop_splitLookup :: Int -> [(Int, Int)] -> Property
1524+
prop_splitLookup n ys =
1525+
let xs = List.nubBy ((==) `on` fst) ys
1526+
(l, x, r) = splitLookup n (fromList xs)
1527+
in valid l .&&.
1528+
valid r .&&.
1529+
x === List.lookup n xs .&&.
1530+
toAscList l === sort [(k, v) | (k,v) <- xs, k < n] .&&.
1531+
toAscList r === sort [(k, v) | (k,v) <- xs, k > n]
1532+
15221533
prop_splitRoot :: IMap -> Bool
15231534
prop_splitRoot s = loop ls && (s == unions ls)
15241535
where

containers/src/Data/IntMap/Internal.hs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2785,26 +2785,26 @@ mapEitherWithKey f0 t0 = toPair $ go f0 t0
27852785
split :: Key -> IntMap a -> (IntMap a, IntMap a)
27862786
split k t =
27872787
case t of
2788-
Bin _ m l r
2788+
Bin p m l r
27892789
| m < 0 ->
27902790
if k >= 0 -- handle negative numbers.
27912791
then
27922792
case go k l of
27932793
(lt :*: gt) ->
2794-
let !lt' = union r lt
2794+
let !lt' = bin p m lt r
27952795
in (lt', gt)
27962796
else
27972797
case go k r of
27982798
(lt :*: gt) ->
2799-
let !gt' = union gt l
2799+
let !gt' = bin p m l gt
28002800
in (lt, gt')
28012801
_ -> case go k t of
28022802
(lt :*: gt) -> (lt, gt)
28032803
where
28042804
go k' t'@(Bin p m l r)
28052805
| nomatch k' p m = if k' > p then t' :*: Nil else Nil :*: t'
2806-
| zero k' m = case go k' l of (lt :*: gt) -> lt :*: union gt r
2807-
| otherwise = case go k' r of (lt :*: gt) -> union l lt :*: gt
2806+
| zero k' m = case go k' l of (lt :*: gt) -> lt :*: bin p m gt r
2807+
| otherwise = case go k' r of (lt :*: gt) -> bin p m l lt :*: gt
28082808
go k' t'@(Tip ky _)
28092809
| k' > ky = (t' :*: Nil)
28102810
| k' < ky = (Nil :*: t')
@@ -2835,11 +2835,11 @@ splitLookup :: Key -> IntMap a -> (IntMap a, Maybe a, IntMap a)
28352835
splitLookup k t =
28362836
case
28372837
case t of
2838-
Bin _ m l r
2838+
Bin p m l r
28392839
| m < 0 ->
28402840
if k >= 0 -- handle negative numbers.
2841-
then mapLT (union r) (go k l)
2842-
else mapGT (`union` l) (go k r)
2841+
then mapLT (flip (bin p m) r) (go k l)
2842+
else mapGT (bin p m l) (go k r)
28432843
_ -> go k t
28442844
of SplitLookup lt fnd gt -> (lt, fnd, gt)
28452845
where
@@ -2848,8 +2848,8 @@ splitLookup k t =
28482848
if k' > p
28492849
then SplitLookup t' Nothing Nil
28502850
else SplitLookup Nil Nothing t'
2851-
| zero k' m = mapGT (`union` r) (go k' l)
2852-
| otherwise = mapLT (union l) (go k' r)
2851+
| zero k' m = mapGT (flip (bin p m) r) (go k' l)
2852+
| otherwise = mapLT (bin p m l) (go k' r)
28532853
go k' t'@(Tip ky y)
28542854
| k' > ky = SplitLookup t' Nothing Nil
28552855
| k' < ky = SplitLookup Nil Nothing t'

containers/src/Data/IntSet/Internal.hs

Lines changed: 42 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -884,23 +884,26 @@ spanAntitone predicate t =
884884
split :: Key -> IntSet -> (IntSet,IntSet)
885885
split x t =
886886
case t of
887-
Bin _ m l r
888-
| m < 0 -> if x >= 0 -- handle negative numbers.
889-
then case go x l of (lt :*: gt) -> let !lt' = union lt r
890-
in (lt', gt)
891-
else case go x r of (lt :*: gt) -> let !gt' = union gt l
892-
in (lt, gt')
893-
_ -> case go x t of
887+
Bin p m l r
888+
| m < 0 ->
889+
if x >= 0 -- handle negative numbers.
890+
then
891+
case go x l of
892+
(lt :*: gt) ->
893+
let !lt' = bin p m lt r
894+
in (lt', gt)
895+
else
896+
case go x r of
897+
(lt :*: gt) ->
898+
let !gt' = bin p m l gt
899+
in (lt, gt')
900+
_ -> case go x t of
894901
(lt :*: gt) -> (lt, gt)
895902
where
896903
go !x' t'@(Bin p m l r)
897-
| match x' p m = if zero x' m
898-
then case go x' l of
899-
(lt :*: gt) -> lt :*: union gt r
900-
else case go x' r of
901-
(lt :*: gt) -> union lt l :*: gt
902-
| otherwise = if x' < p then (Nil :*: t')
903-
else (t' :*: Nil)
904+
| nomatch x' p m = if x' < p then (Nil :*: t') else (t' :*: Nil)
905+
| zero x' m = case go x' l of (lt :*: gt) -> lt :*: bin p m gt r
906+
| otherwise = case go x' r of (lt :*: gt) -> bin p m l lt :*: gt
904907
go x' t'@(Tip kx' bm)
905908
| kx' > x' = (Nil :*: t')
906909
-- equivalent to kx' > prefixOf x'
@@ -915,22 +918,33 @@ split x t =
915918
splitMember :: Key -> IntSet -> (IntSet,Bool,IntSet)
916919
splitMember x t =
917920
case t of
918-
Bin _ m l r | m < 0 -> if x >= 0
919-
then case go x l of
920-
(lt, fnd, gt) -> let !lt' = union lt r
921-
in (lt', fnd, gt)
922-
else case go x r of
923-
(lt, fnd, gt) -> let !gt' = union gt l
924-
in (lt, fnd, gt')
925-
_ -> go x t
921+
Bin p m l r
922+
| m < 0 ->
923+
if x >= 0 -- handle negative numbers.
924+
then
925+
case go x l of
926+
(lt, fnd, gt) ->
927+
let !lt' = bin p m lt r
928+
in (lt', fnd, gt)
929+
else
930+
case go x r of
931+
(lt, fnd, gt) ->
932+
let !gt' = bin p m l gt
933+
in (lt, fnd, gt')
934+
_ -> go x t
926935
where
927936
go x' t'@(Bin p m l r)
928-
| match x' p m = if zero x' m
929-
then case go x' l of
930-
(lt, fnd, gt) -> (lt, fnd, union gt r)
931-
else case go x' r of
932-
(lt, fnd, gt) -> (union lt l, fnd, gt)
933-
| otherwise = if x' < p then (Nil, False, t') else (t', False, Nil)
937+
| nomatch x' p m = if x' < p then (Nil, False, t') else (t', False, Nil)
938+
| zero x' m =
939+
case go x' l of
940+
(lt, fnd, gt) ->
941+
let !gt' = bin p m gt r
942+
in (lt, fnd, gt')
943+
| otherwise =
944+
case go x' r of
945+
(lt, fnd, gt) ->
946+
let !lt' = bin p m l lt
947+
in (lt', fnd, gt)
934948
go x' t'@(Tip kx' bm)
935949
| kx' > x' = (Nil, False, t')
936950
-- equivalent to kx' > prefixOf x'

0 commit comments

Comments
 (0)