Skip to content

Commit f731400

Browse files
committed
Reduce code size with unboxed unary tuples
Experimentally make most strict and lazy functions share code using unboxed unary tuples. I fear we may find this too expensive, but it's an idea. Needs heavy benchmarking. Addresses haskell-unordered-containers#64
1 parent d672a11 commit f731400

File tree

2 files changed

+112
-227
lines changed

2 files changed

+112
-227
lines changed

Data/HashMap/Base.hs

+103-11
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ module Data.HashMap.Base
107107
, insertModifying
108108
, ptrEq
109109
, adjust#
110+
, unionWithKey#
111+
, unsafeInsertWith
110112
) where
111113

112114
#if __GLASGOW_HASKELL__ < 710
@@ -650,7 +652,7 @@ insert' h0 k0 v0 m0 = go h0 k0 v0 0 m0
650652
else Full (update16 ary i st')
651653
where i = index h s
652654
go h k x s t@(Collision hy v)
653-
| h == hy = Collision h (updateOrSnocWith const k x v)
655+
| h == hy = Collision h (updateOrSnocWith (\v1 _ -> (# v1 #)) k x v)
654656
| otherwise = go h k x s $ BitmapIndexed (mask hy s) (A.singleton t)
655657
{-# INLINABLE insert' #-}
656658

@@ -773,7 +775,7 @@ unsafeInsert k0 v0 m0 = runST (go h0 k0 v0 0 m0)
773775
return t
774776
where i = index h s
775777
go h k x s t@(Collision hy v)
776-
| h == hy = return $! Collision h (updateOrSnocWith const k x v)
778+
| h == hy = return $! Collision h (updateOrSnocWith (\v1 _ -> (# v1 #)) k x v)
777779
| otherwise = go h k x s $ BitmapIndexed (mask hy s) (A.singleton t)
778780
{-# INLINABLE unsafeInsert #-}
779781

@@ -882,7 +884,7 @@ insertModifyingArr x f k0 ary0 = go k0 ary0 0 (A.length ary0)
882884

883885
-- | In-place update version of insertWith
884886
unsafeInsertWith :: forall k v. (Eq k, Hashable k)
885-
=> (v -> v -> v) -> k -> v -> HashMap k v
887+
=> (v -> v -> (# v #)) -> k -> v -> HashMap k v
886888
-> HashMap k v
887889
unsafeInsertWith f k0 v0 m0 = runST (go h0 k0 v0 0 m0)
888890
where
@@ -891,7 +893,7 @@ unsafeInsertWith f k0 v0 m0 = runST (go h0 k0 v0 0 m0)
891893
go !h !k x !_ Empty = return $! Leaf h (L k x)
892894
go h k x s (Leaf hy l@(L ky y))
893895
| hy == h = if ky == k
894-
then return $! Leaf h (L k (f x y))
896+
then case f x y of (# v #) -> return $! Leaf h (L k v)
895897
else return $! collision h l (L k x)
896898
| otherwise = two s h k x hy ky y
897899
go h k x s t@(BitmapIndexed b ary)
@@ -1256,6 +1258,9 @@ unionWith f = unionWithKey (const f)
12561258
-- result.
12571259
unionWithKey :: (Eq k, Hashable k) => (k -> v -> v -> v) -> HashMap k v -> HashMap k v
12581260
-> HashMap k v
1261+
unionWithKey f m = unionWithKey# (\k v1 v2 -> (# f k v1 v2 #)) m
1262+
{-# INLINE unionWithKey #-}
1263+
{-
12591264
unionWithKey f = go 0
12601265
where
12611266
-- empty vs. anything
@@ -1337,6 +1342,93 @@ unionWithKey f = go 0
13371342
m1 = mask h1 s
13381343
m2 = mask h2 s
13391344
{-# INLINE unionWithKey #-}
1345+
-}
1346+
1347+
1348+
unionWithKey# :: (Eq k, Hashable k) => (k -> v -> v -> (# v #)) -> HashMap k v -> HashMap k v
1349+
-> HashMap k v
1350+
unionWithKey# f = go 0
1351+
where
1352+
-- empty vs. anything
1353+
go !_ t1 Empty = t1
1354+
go _ Empty t2 = t2
1355+
-- leaf vs. leaf
1356+
go s t1@(Leaf h1 l1@(L k1 v1)) t2@(Leaf h2 l2@(L k2 v2))
1357+
| h1 == h2 = if k1 == k2
1358+
then case f k1 v1 v2 of (# v #) -> Leaf h1 (L k1 v)
1359+
else collision h1 l1 l2
1360+
| otherwise = goDifferentHash s h1 h2 t1 t2
1361+
go s t1@(Leaf h1 (L k1 v1)) t2@(Collision h2 ls2)
1362+
| h1 == h2 = Collision h1 (updateOrSnocWithKey f k1 v1 ls2)
1363+
| otherwise = goDifferentHash s h1 h2 t1 t2
1364+
go s t1@(Collision h1 ls1) t2@(Leaf h2 (L k2 v2))
1365+
| h1 == h2 = Collision h1 (updateOrSnocWithKey (\q w x -> f q x w) k2 v2 ls1)
1366+
| otherwise = goDifferentHash s h1 h2 t1 t2
1367+
go s t1@(Collision h1 ls1) t2@(Collision h2 ls2)
1368+
| h1 == h2 = Collision h1 (updateOrConcatWithKey f ls1 ls2)
1369+
| otherwise = goDifferentHash s h1 h2 t1 t2
1370+
-- branch vs. branch
1371+
go s (BitmapIndexed b1 ary1) (BitmapIndexed b2 ary2) =
1372+
let b' = b1 .|. b2
1373+
ary' = unionArrayBy (go (s+bitsPerSubkey)) b1 b2 ary1 ary2
1374+
in bitmapIndexedOrFull b' ary'
1375+
go s (BitmapIndexed b1 ary1) (Full ary2) =
1376+
let ary' = unionArrayBy (go (s+bitsPerSubkey)) b1 fullNodeMask ary1 ary2
1377+
in Full ary'
1378+
go s (Full ary1) (BitmapIndexed b2 ary2) =
1379+
let ary' = unionArrayBy (go (s+bitsPerSubkey)) fullNodeMask b2 ary1 ary2
1380+
in Full ary'
1381+
go s (Full ary1) (Full ary2) =
1382+
let ary' = unionArrayBy (go (s+bitsPerSubkey)) fullNodeMask fullNodeMask
1383+
ary1 ary2
1384+
in Full ary'
1385+
-- leaf vs. branch
1386+
go s (BitmapIndexed b1 ary1) t2
1387+
| b1 .&. m2 == 0 = let ary' = A.insert ary1 i t2
1388+
b' = b1 .|. m2
1389+
in bitmapIndexedOrFull b' ary'
1390+
| otherwise = let ary' = A.updateWith' ary1 i $ \st1 ->
1391+
go (s+bitsPerSubkey) st1 t2
1392+
in BitmapIndexed b1 ary'
1393+
where
1394+
h2 = leafHashCode t2
1395+
m2 = mask h2 s
1396+
i = sparseIndex b1 m2
1397+
go s t1 (BitmapIndexed b2 ary2)
1398+
| b2 .&. m1 == 0 = let ary' = A.insert ary2 i $! t1
1399+
b' = b2 .|. m1
1400+
in bitmapIndexedOrFull b' ary'
1401+
| otherwise = let ary' = A.updateWith' ary2 i $ \st2 ->
1402+
go (s+bitsPerSubkey) t1 st2
1403+
in BitmapIndexed b2 ary'
1404+
where
1405+
h1 = leafHashCode t1
1406+
m1 = mask h1 s
1407+
i = sparseIndex b2 m1
1408+
go s (Full ary1) t2 =
1409+
let h2 = leafHashCode t2
1410+
i = index h2 s
1411+
ary' = update16With' ary1 i $ \st1 -> go (s+bitsPerSubkey) st1 t2
1412+
in Full ary'
1413+
go s t1 (Full ary2) =
1414+
let h1 = leafHashCode t1
1415+
i = index h1 s
1416+
ary' = update16With' ary2 i $ \st2 -> go (s+bitsPerSubkey) t1 st2
1417+
in Full ary'
1418+
1419+
leafHashCode (Leaf h _) = h
1420+
leafHashCode (Collision h _) = h
1421+
leafHashCode _ = error "leafHashCode"
1422+
1423+
goDifferentHash s h1 h2 t1 t2
1424+
| m1 == m2 = BitmapIndexed m1 (A.singleton $! go (s+bitsPerSubkey) t1 t2)
1425+
| m1 < m2 = BitmapIndexed (m1 .|. m2) (A.pair t1 t2)
1426+
| otherwise = BitmapIndexed (m1 .|. m2) (A.pair t2 t1)
1427+
where
1428+
m1 = mask h1 s
1429+
m2 = mask h2 s
1430+
{-# INLINE unionWithKey# #-}
1431+
13401432

13411433
-- | Strict in the result of @f@.
13421434
unionArrayBy :: (a -> a -> a) -> Bitmap -> Bitmap -> A.Array a -> A.Array a
@@ -1667,7 +1759,7 @@ fromList = L.foldl' (\ m (k, v) -> unsafeInsert k v m) empty
16671759
-- | /O(n*log n)/ Construct a map from a list of elements. Uses
16681760
-- the provided function to merge duplicate entries.
16691761
fromListWith :: (Eq k, Hashable k) => (v -> v -> v) -> [(k, v)] -> HashMap k v
1670-
fromListWith f = L.foldl' (\ m (k, v) -> unsafeInsertWith f k v m) empty
1762+
fromListWith f = L.foldl' (\ m (k, v) -> unsafeInsertWith (\x y -> (# f x y #)) k v m) empty
16711763
{-# INLINE fromListWith #-}
16721764

16731765
------------------------------------------------------------------------
@@ -1719,12 +1811,12 @@ updateWith# f k0 ary0 = go k0 ary0 0 (A.length ary0)
17191811
| otherwise -> go k ary (i+1) n
17201812
{-# INLINABLE updateWith# #-}
17211813

1722-
updateOrSnocWith :: Eq k => (v -> v -> v) -> k -> v -> A.Array (Leaf k v)
1814+
updateOrSnocWith :: Eq k => (v -> v -> (# v #)) -> k -> v -> A.Array (Leaf k v)
17231815
-> A.Array (Leaf k v)
17241816
updateOrSnocWith f = updateOrSnocWithKey (const f)
17251817
{-# INLINABLE updateOrSnocWith #-}
17261818

1727-
updateOrSnocWithKey :: Eq k => (k -> v -> v -> v) -> k -> v -> A.Array (Leaf k v)
1819+
updateOrSnocWithKey :: Eq k => (k -> v -> v -> (# v #)) -> k -> v -> A.Array (Leaf k v)
17281820
-> A.Array (Leaf k v)
17291821
updateOrSnocWithKey f k0 v0 ary0 = go k0 v0 ary0 0 (A.length ary0)
17301822
where
@@ -1736,15 +1828,15 @@ updateOrSnocWithKey f k0 v0 ary0 = go k0 v0 ary0 0 (A.length ary0)
17361828
A.write mary n (L k v)
17371829
return mary
17381830
| otherwise = case A.index ary i of
1739-
(L kx y) | k == kx -> A.update ary i (L k (f k v y))
1831+
(L kx y) | k == kx -> case f k v y of (# y' #) -> A.update ary i (L k y')
17401832
| otherwise -> go k v ary (i+1) n
17411833
{-# INLINABLE updateOrSnocWithKey #-}
17421834

1743-
updateOrConcatWith :: Eq k => (v -> v -> v) -> A.Array (Leaf k v) -> A.Array (Leaf k v) -> A.Array (Leaf k v)
1835+
updateOrConcatWith :: Eq k => (v -> v -> (# v #)) -> A.Array (Leaf k v) -> A.Array (Leaf k v) -> A.Array (Leaf k v)
17441836
updateOrConcatWith f = updateOrConcatWithKey (const f)
17451837
{-# INLINABLE updateOrConcatWith #-}
17461838

1747-
updateOrConcatWithKey :: Eq k => (k -> v -> v -> v) -> A.Array (Leaf k v) -> A.Array (Leaf k v) -> A.Array (Leaf k v)
1839+
updateOrConcatWithKey :: Eq k => (k -> v -> v -> (# v #)) -> A.Array (Leaf k v) -> A.Array (Leaf k v) -> A.Array (Leaf k v)
17481840
updateOrConcatWithKey f ary1 ary2 = A.run $ do
17491841
-- first: look up the position of each element of ary2 in ary1
17501842
let indices = A.map (\(L k _) -> indexOf k ary1) ary2
@@ -1763,7 +1855,7 @@ updateOrConcatWithKey f ary1 ary2 = A.run $ do
17631855
Just i1 -> do -- key occurs in both arrays, store combination in position i1
17641856
L k v1 <- A.indexM ary1 i1
17651857
L _ v2 <- A.indexM ary2 i2
1766-
A.write mary i1 (L k (f k v1 v2))
1858+
case f k v1 v2 of (# v' #) -> A.write mary i1 (L k v')
17671859
go iEnd (i2+1)
17681860
Nothing -> do -- key is only in ary2, append to end
17691861
A.write mary iEnd =<< A.indexM ary2 i2

0 commit comments

Comments
 (0)