From 9b062486bfadac698fbbff745af874aa2a408fc2 Mon Sep 17 00:00:00 2001 From: Brian Shu Date: Thu, 26 May 2022 12:37:39 -0400 Subject: [PATCH 01/17] alter now runs in one pass --- Data/HashMap/Internal.hs | 111 ++++++++++++++++++++++++++++--------- unordered-containers.cabal | 3 +- 2 files changed, 88 insertions(+), 26 deletions(-) diff --git a/Data/HashMap/Internal.hs b/Data/HashMap/Internal.hs index 13d3d0f5..73f147a6 100644 --- a/Data/HashMap/Internal.hs +++ b/Data/HashMap/Internal.hs @@ -835,7 +835,7 @@ insert' h0 k0 v0 m0 = go h0 k0 v0 0 m0 where i = index h s go h k x s t@(Collision hy v) | h == hy = Collision h (updateOrSnocWith (\a _ -> (# a #)) k x v) - | otherwise = go h k x s $ BitmapIndexed (mask hy s) (A.singleton t) + | otherwise = runST $ two s h k x hy t {-# INLINABLE insert' #-} -- | Insert optimized for the case when we know the key is not in the map. @@ -1260,11 +1260,10 @@ adjust# f k0 m0 = go h0 k0 0 m0 -- | \(O(\log n)\) The expression @('update' f k map)@ updates the value @x@ at @k@ -- (if it is in the map). If @(f x)@ is 'Nothing', the element is deleted. -- If it is @('Just' y)@, the key @k@ is bound to the new value @y@. -update :: (Eq k, Hashable k) => (a -> Maybe a) -> k -> HashMap k a -> HashMap k a -update f = alter (>>= f) +update :: (Eq k, Hashable k, Show k, Show a) => (a -> Maybe a) -> k -> HashMap k a -> HashMap k a +update f = Exts.inline alter (>>= f) {-# INLINABLE update #-} - -- | \(O(\log n)\) The expression @('alter' f k map)@ alters the value @x@ at @k@, or -- absence thereof. -- @@ -1273,27 +1272,89 @@ update f = alter (>>= f) -- @ -- 'lookup' k ('alter' f k m) = f ('lookup' k m) -- @ -alter :: (Eq k, Hashable k) => (Maybe v -> Maybe v) -> k -> HashMap k v -> HashMap k v -alter f k m = - let !h = hash k - !lookupRes = lookupRecordCollision h k m - in case f (lookupResToMaybe lookupRes) of - Nothing -> case lookupRes of - Absent -> m - Present _ collPos -> deleteKeyExists collPos h k m - Just v' -> case lookupRes of - Absent -> insertNewKey h k v' m - Present v collPos -> - if v `ptrEq` v' - then m - else insertKeyExists collPos h k v' m -{-# INLINABLE alter #-} - --- | \(O(\log n)\) The expression @('alterF' f k map)@ alters the value @x@ at --- @k@, or absence thereof. --- --- 'alterF' can be used to insert, delete, or update a value in a map. --- +alter :: (Eq k, Hashable k, Show k, Show v) => (Maybe v -> Maybe v) -> k -> HashMap k v -> HashMap k v +alter f k = alter' f (hash k) k +{-# INLINEABLE alter #-} + +alter' :: (Eq k, Show v) => (Maybe v -> Maybe v) -> Hash -> k -> HashMap k v -> HashMap k v +alter' f h0 k0 m0 = go h0 k0 0 m0 + where + go !h !k !_ Empty = case f Nothing of + Nothing -> Empty + Just v -> Leaf h $ L k v + go h k s t@(Leaf hy l@(L ky v)) + | hy == h = + if ky == k + then case f $ Just v of + Nothing -> Empty + Just v' + | v `ptrEq` v' -> t + | otherwise -> Leaf h $ L k v' + else do + case f Nothing of + Nothing -> t + Just v' -> collision h l $ L k v' + | otherwise = case f Nothing of + Nothing -> t + Just v' -> runST $ two s h k v' hy t + go h k s t@(BitmapIndexed b ary) + | b .&. m == 0 = case f Nothing of + Nothing -> t + Just v' -> bitmapIndexedOrFull (b .|. m) $! A.insert ary i $! Leaf h $! L k v' + | otherwise = do + let !st = A.index ary i + !st' = go h k (nextShift s) st + if st' `ptrEq` st + then t + else case st' of + Empty + | A.length ary == 1 -> Empty + | A.length ary == 2 -> + case (i, A.index ary 0, A.index ary 1) of + (0, _, l) | isLeafOrCollision l -> l + (1, l, _) | isLeafOrCollision l -> l + _ -> bIndexed + | otherwise -> bIndexed + where + bIndexed = BitmapIndexed (b .&. complement m) (A.delete ary i) + l | isLeafOrCollision l && A.length ary == 1 -> l + _ -> BitmapIndexed b (A.update ary i st') + where + m = mask h s + i = sparseIndex b m + go h k s t@(Full ary) = do + let !st = A.index ary i + !st' = go h k (nextShift s) st + if st' `ptrEq` st + then t + else case st' of + Empty -> + let ary' = A.delete ary i + bm = fullBitmap .&. complement (1 `unsafeShiftL` i) + in BitmapIndexed bm ary' + _ -> Full (A.update ary i st') + where + i = index h s + go h k s t@(Collision hy ls) + | h == hy = case indexOf k ls of + Just i -> do + let (# L _ v #) = A.index# ls i + case f $ Just v of + Nothing + | A.length ls == 2 -> + if i == 0 + then Leaf h (A.index ls 1) + else Leaf h (A.index ls 0) + | otherwise -> Collision hy (A.delete ls i) + Just v' -> Collision hy $ A.update ls i $ L k v' + Nothing -> case f Nothing of + Nothing -> t + Just v' -> Collision hy $ A.snoc ls $ L k v' + | otherwise = case f Nothing of + Nothing -> t + Just v' -> runST $ two s h k v' hy t +{-# INLINE alter' #-} + -- Note: 'alterF' is a flipped version of the 'at' combinator from -- . -- diff --git a/unordered-containers.cabal b/unordered-containers.cabal index 228eb53e..ac3e7d56 100644 --- a/unordered-containers.cabal +++ b/unordered-containers.cabal @@ -69,7 +69,8 @@ library MagicHash, BangPatterns - ghc-options: -Wall -O2 -fwarn-tabs -ferror-spans + -- ghc-options: -Wall -O2 -fwarn-tabs -ferror-spans + ghc-options: -Wall -fwarn-tabs -ferror-spans -- For dumping the generated code: -- ghc-options: -ddump-simpl -ddump-stg-final -ddump-cmm -ddump-asm -ddump-to-file From 8df6d0e8e807c1865c1642aee9d4e29f525cd45b Mon Sep 17 00:00:00 2001 From: Brian Shu Date: Thu, 26 May 2022 13:29:36 -0400 Subject: [PATCH 02/17] remove redundant constraints --- Data/HashMap/Internal.hs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Data/HashMap/Internal.hs b/Data/HashMap/Internal.hs index 73f147a6..4ee800e2 100644 --- a/Data/HashMap/Internal.hs +++ b/Data/HashMap/Internal.hs @@ -1260,7 +1260,7 @@ adjust# f k0 m0 = go h0 k0 0 m0 -- | \(O(\log n)\) The expression @('update' f k map)@ updates the value @x@ at @k@ -- (if it is in the map). If @(f x)@ is 'Nothing', the element is deleted. -- If it is @('Just' y)@, the key @k@ is bound to the new value @y@. -update :: (Eq k, Hashable k, Show k, Show a) => (a -> Maybe a) -> k -> HashMap k a -> HashMap k a +update :: (Eq k, Hashable k) => (a -> Maybe a) -> k -> HashMap k a -> HashMap k a update f = Exts.inline alter (>>= f) {-# INLINABLE update #-} @@ -1272,11 +1272,11 @@ update f = Exts.inline alter (>>= f) -- @ -- 'lookup' k ('alter' f k m) = f ('lookup' k m) -- @ -alter :: (Eq k, Hashable k, Show k, Show v) => (Maybe v -> Maybe v) -> k -> HashMap k v -> HashMap k v +alter :: (Eq k, Hashable k) => (Maybe v -> Maybe v) -> k -> HashMap k v -> HashMap k v alter f k = alter' f (hash k) k {-# INLINEABLE alter #-} -alter' :: (Eq k, Show v) => (Maybe v -> Maybe v) -> Hash -> k -> HashMap k v -> HashMap k v +alter' :: Eq k => (Maybe v -> Maybe v) -> Hash -> k -> HashMap k v -> HashMap k v alter' f h0 k0 m0 = go h0 k0 0 m0 where go !h !k !_ Empty = case f Nothing of From 8e02578af28baa6d8b4c49bf7462cce87bd872d9 Mon Sep 17 00:00:00 2001 From: oberblastmeister <61095988+oberblastmeister@users.noreply.github.com> Date: Fri, 27 May 2022 13:50:54 -0400 Subject: [PATCH 03/17] Update Data/HashMap/Internal.hs Co-authored-by: Simon Jakobi --- Data/HashMap/Internal.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Data/HashMap/Internal.hs b/Data/HashMap/Internal.hs index 4ee800e2..cfe08a22 100644 --- a/Data/HashMap/Internal.hs +++ b/Data/HashMap/Internal.hs @@ -1274,7 +1274,7 @@ update f = Exts.inline alter (>>= f) -- @ alter :: (Eq k, Hashable k) => (Maybe v -> Maybe v) -> k -> HashMap k v -> HashMap k v alter f k = alter' f (hash k) k -{-# INLINEABLE alter #-} +{-# INLINABLE alter #-} alter' :: Eq k => (Maybe v -> Maybe v) -> Hash -> k -> HashMap k v -> HashMap k v alter' f h0 k0 m0 = go h0 k0 0 m0 From 61eb5867f8d0e643bfe6bd2f3de40bcf4e198729 Mon Sep 17 00:00:00 2001 From: Brian Shu Date: Sat, 28 May 2022 10:05:04 -0400 Subject: [PATCH 04/17] add to strict HashMap --- Data/HashMap/Internal.hs | 2 +- Data/HashMap/Internal/Strict.hs | 18 ++++-------------- unordered-containers.cabal | 4 ++-- 3 files changed, 7 insertions(+), 17 deletions(-) diff --git a/Data/HashMap/Internal.hs b/Data/HashMap/Internal.hs index cfe08a22..e74d2e3a 100644 --- a/Data/HashMap/Internal.hs +++ b/Data/HashMap/Internal.hs @@ -1273,7 +1273,7 @@ update f = Exts.inline alter (>>= f) -- 'lookup' k ('alter' f k m) = f ('lookup' k m) -- @ alter :: (Eq k, Hashable k) => (Maybe v -> Maybe v) -> k -> HashMap k v -> HashMap k v -alter f k = alter' f (hash k) k +alter f k = Exts.inline alter' f (hash k) k {-# INLINABLE alter #-} alter' :: Eq k => (Maybe v -> Maybe v) -> Hash -> k -> HashMap k v -> HashMap k v diff --git a/Data/HashMap/Internal/Strict.hs b/Data/HashMap/Internal/Strict.hs index 42caac16..c3fedea6 100644 --- a/Data/HashMap/Internal/Strict.hs +++ b/Data/HashMap/Internal/Strict.hs @@ -299,7 +299,7 @@ adjust f k0 m0 = go h0 k0 0 m0 -- (if it is in the map). If @(f x)@ is 'Nothing', the element is deleted. -- If it is @('Just' y)@, the key @k@ is bound to the new value @y@. update :: (Eq k, Hashable k) => (a -> Maybe a) -> k -> HashMap k a -> HashMap k a -update f = alter (>>= f) +update f = Exts.inline alter (>>= f) {-# INLINABLE update #-} -- | \(O(\log n)\) The expression @('alter' f k map)@ alters the value @x@ at @k@, or @@ -311,19 +311,9 @@ update f = alter (>>= f) -- 'lookup' k ('alter' f k m) = f ('lookup' k m) -- @ alter :: (Eq k, Hashable k) => (Maybe v -> Maybe v) -> k -> HashMap k v -> HashMap k v -alter f k m = - let !h = hash k - !lookupRes = HM.lookupRecordCollision h k m - in case f (HM.lookupResToMaybe lookupRes) of - Nothing -> case lookupRes of - Absent -> m - Present _ collPos -> HM.deleteKeyExists collPos h k m - Just !v' -> case lookupRes of - Absent -> HM.insertNewKey h k v' m - Present v collPos -> - if v `ptrEq` v' - then m - else HM.insertKeyExists collPos h k v' m +alter f = Exts.inline HM.alter $ \m -> case f m of + Nothing -> Nothing + Just !x -> Just x {-# INLINABLE alter #-} -- | \(O(\log n)\) The expression (@'alterF' f k map@) alters the value @x@ at diff --git a/unordered-containers.cabal b/unordered-containers.cabal index ac3e7d56..afa0edd8 100644 --- a/unordered-containers.cabal +++ b/unordered-containers.cabal @@ -69,8 +69,8 @@ library MagicHash, BangPatterns - -- ghc-options: -Wall -O2 -fwarn-tabs -ferror-spans - ghc-options: -Wall -fwarn-tabs -ferror-spans + ghc-options: -Wall -O2 -fwarn-tabs -ferror-spans + -- ghc-options: -Wall -fwarn-tabs -ferror-spans -- For dumping the generated code: -- ghc-options: -ddump-simpl -ddump-stg-final -ddump-cmm -ddump-asm -ddump-to-file From 429fb1d74ea7be12d5e9c575da85ae8fd54820d5 Mon Sep 17 00:00:00 2001 From: Brian Shu Date: Sat, 28 May 2022 10:07:10 -0400 Subject: [PATCH 05/17] bang pattern --- Data/HashMap/Internal.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Data/HashMap/Internal.hs b/Data/HashMap/Internal.hs index e74d2e3a..198b95aa 100644 --- a/Data/HashMap/Internal.hs +++ b/Data/HashMap/Internal.hs @@ -1338,7 +1338,7 @@ alter' f h0 k0 m0 = go h0 k0 0 m0 go h k s t@(Collision hy ls) | h == hy = case indexOf k ls of Just i -> do - let (# L _ v #) = A.index# ls i + let !(L _ v) = A.index ls i case f $ Just v of Nothing | A.length ls == 2 -> From 5779cc842555ffe3856445d1417f946054493505 Mon Sep 17 00:00:00 2001 From: Brian Shu Date: Mon, 6 Jun 2022 10:56:19 -0400 Subject: [PATCH 06/17] remove use of two for now for insert' --- Data/HashMap/Internal.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Data/HashMap/Internal.hs b/Data/HashMap/Internal.hs index 198b95aa..b40c59ce 100644 --- a/Data/HashMap/Internal.hs +++ b/Data/HashMap/Internal.hs @@ -835,7 +835,7 @@ insert' h0 k0 v0 m0 = go h0 k0 v0 0 m0 where i = index h s go h k x s t@(Collision hy v) | h == hy = Collision h (updateOrSnocWith (\a _ -> (# a #)) k x v) - | otherwise = runST $ two s h k x hy t + | otherwise = go h k x s $ BitmapIndexed (mask hy s) (A.singleton t) {-# INLINABLE insert' #-} -- | Insert optimized for the case when we know the key is not in the map. From 9a3553d21e6516d15e2e1e22e19c5a1f870a2896 Mon Sep 17 00:00:00 2001 From: Simon Jakobi Date: Tue, 11 Nov 2025 18:19:02 +0100 Subject: [PATCH 07/17] Use A.index# instead of the removed A.index function --- Data/HashMap/Internal.hs | 77 ++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 39 deletions(-) diff --git a/Data/HashMap/Internal.hs b/Data/HashMap/Internal.hs index b40c59ce..bfb93c77 100644 --- a/Data/HashMap/Internal.hs +++ b/Data/HashMap/Internal.hs @@ -1301,52 +1301,51 @@ alter' f h0 k0 m0 = go h0 k0 0 m0 | b .&. m == 0 = case f Nothing of Nothing -> t Just v' -> bitmapIndexedOrFull (b .|. m) $! A.insert ary i $! Leaf h $! L k v' - | otherwise = do - let !st = A.index ary i - !st' = go h k (nextShift s) st - if st' `ptrEq` st - then t - else case st' of - Empty - | A.length ary == 1 -> Empty - | A.length ary == 2 -> - case (i, A.index ary 0, A.index ary 1) of - (0, _, l) | isLeafOrCollision l -> l - (1, l, _) | isLeafOrCollision l -> l - _ -> bIndexed - | otherwise -> bIndexed - where - bIndexed = BitmapIndexed (b .&. complement m) (A.delete ary i) - l | isLeafOrCollision l && A.length ary == 1 -> l - _ -> BitmapIndexed b (A.update ary i st') + | otherwise = + case A.index# ary i of + (# !st #) -> do + let !st' = go h k (nextShift s) st + if st' `ptrEq` st + then t + else case st' of + Empty + | A.length ary == 2 + , (# l #) <- A.index# ary (otherOfOneOrZero i) + , isLeafOrCollision l + -> l + | otherwise -> bIndexed + where + bIndexed = BitmapIndexed (b .&. complement m) (A.delete ary i) + l | isLeafOrCollision l && A.length ary == 1 -> l + _ -> BitmapIndexed b (A.update ary i st') where m = mask h s i = sparseIndex b m go h k s t@(Full ary) = do - let !st = A.index ary i - !st' = go h k (nextShift s) st - if st' `ptrEq` st - then t - else case st' of - Empty -> - let ary' = A.delete ary i - bm = fullBitmap .&. complement (1 `unsafeShiftL` i) - in BitmapIndexed bm ary' - _ -> Full (A.update ary i st') - where - i = index h s + case A.index# ary i of + (# !st #) -> do + let !st' = go h k (nextShift s) st + if st' `ptrEq` st + then t + else case st' of + Empty -> + let ary' = A.delete ary i + bm = fullBitmap .&. complement (1 `unsafeShiftL` i) + in BitmapIndexed bm ary' + _ -> Full (A.update ary i st') + where i = index h s go h k s t@(Collision hy ls) | h == hy = case indexOf k ls of Just i -> do - let !(L _ v) = A.index ls i - case f $ Just v of - Nothing - | A.length ls == 2 -> - if i == 0 - then Leaf h (A.index ls 1) - else Leaf h (A.index ls 0) - | otherwise -> Collision hy (A.delete ls i) - Just v' -> Collision hy $ A.update ls i $ L k v' + case A.index# ls i of + (# L _ v #) -> + case f $ Just v of + Nothing + | A.length ls == 2 -> + case A.index# ls (otherOfOneOrZero i) of + (# l #) -> Leaf h l + | otherwise -> Collision hy (A.delete ls i) + Just v' -> Collision hy $ A.update ls i $ L k v' Nothing -> case f Nothing of Nothing -> t Just v' -> Collision hy $ A.snoc ls $ L k v' From 2e7b1c492b30c15baab46b36f17a955ecf8b2e26 Mon Sep 17 00:00:00 2001 From: Simon Jakobi Date: Tue, 11 Nov 2025 18:30:22 +0100 Subject: [PATCH 08/17] Change position of pointer-equality check --- Data/HashMap/Internal.hs | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/Data/HashMap/Internal.hs b/Data/HashMap/Internal.hs index bfb93c77..26d87c85 100644 --- a/Data/HashMap/Internal.hs +++ b/Data/HashMap/Internal.hs @@ -1303,21 +1303,17 @@ alter' f h0 k0 m0 = go h0 k0 0 m0 Just v' -> bitmapIndexedOrFull (b .|. m) $! A.insert ary i $! Leaf h $! L k v' | otherwise = case A.index# ary i of - (# !st #) -> do - let !st' = go h k (nextShift s) st - if st' `ptrEq` st - then t - else case st' of - Empty - | A.length ary == 2 - , (# l #) <- A.index# ary (otherOfOneOrZero i) - , isLeafOrCollision l - -> l - | otherwise -> bIndexed - where - bIndexed = BitmapIndexed (b .&. complement m) (A.delete ary i) - l | isLeafOrCollision l && A.length ary == 1 -> l - _ -> BitmapIndexed b (A.update ary i st') + (# !st #) -> case go h k (nextShift s) st of + Empty + | A.length ary == 2 + , (# l #) <- A.index# ary (otherOfOneOrZero i) + , isLeafOrCollision l + -> l + | otherwise -> BitmapIndexed (b .&. complement m) (A.delete ary i) + st' + | isLeafOrCollision st' && A.length ary == 1 -> st' + | st' `ptrEq` st -> t + | otherwise -> BitmapIndexed b (A.update ary i st') where m = mask h s i = sparseIndex b m From 915cc3d56eadf513abb9675d9d9a6c4c13587269 Mon Sep 17 00:00:00 2001 From: Simon Jakobi Date: Tue, 11 Nov 2025 18:35:50 +0100 Subject: [PATCH 09/17] Rename inner `go` function --- Data/HashMap/Internal.hs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Data/HashMap/Internal.hs b/Data/HashMap/Internal.hs index 26d87c85..852ed144 100644 --- a/Data/HashMap/Internal.hs +++ b/Data/HashMap/Internal.hs @@ -1277,12 +1277,12 @@ alter f k = Exts.inline alter' f (hash k) k {-# INLINABLE alter #-} alter' :: Eq k => (Maybe v -> Maybe v) -> Hash -> k -> HashMap k v -> HashMap k v -alter' f h0 k0 m0 = go h0 k0 0 m0 +alter' f h0 k0 m0 = go_alter' h0 k0 0 m0 where - go !h !k !_ Empty = case f Nothing of + go_alter' !h !k !_ Empty = case f Nothing of Nothing -> Empty Just v -> Leaf h $ L k v - go h k s t@(Leaf hy l@(L ky v)) + go_alter' h k s t@(Leaf hy l@(L ky v)) | hy == h = if ky == k then case f $ Just v of @@ -1297,13 +1297,13 @@ alter' f h0 k0 m0 = go h0 k0 0 m0 | otherwise = case f Nothing of Nothing -> t Just v' -> runST $ two s h k v' hy t - go h k s t@(BitmapIndexed b ary) + go_alter' h k s t@(BitmapIndexed b ary) | b .&. m == 0 = case f Nothing of Nothing -> t Just v' -> bitmapIndexedOrFull (b .|. m) $! A.insert ary i $! Leaf h $! L k v' | otherwise = case A.index# ary i of - (# !st #) -> case go h k (nextShift s) st of + (# !st #) -> case go_alter' h k (nextShift s) st of Empty | A.length ary == 2 , (# l #) <- A.index# ary (otherOfOneOrZero i) @@ -1317,10 +1317,10 @@ alter' f h0 k0 m0 = go h0 k0 0 m0 where m = mask h s i = sparseIndex b m - go h k s t@(Full ary) = do + go_alter' h k s t@(Full ary) = do case A.index# ary i of (# !st #) -> do - let !st' = go h k (nextShift s) st + let !st' = go_alter' h k (nextShift s) st if st' `ptrEq` st then t else case st' of @@ -1330,7 +1330,7 @@ alter' f h0 k0 m0 = go h0 k0 0 m0 in BitmapIndexed bm ary' _ -> Full (A.update ary i st') where i = index h s - go h k s t@(Collision hy ls) + go_alter' h k s t@(Collision hy ls) | h == hy = case indexOf k ls of Just i -> do case A.index# ls i of From 0c2ffcf8a679d2e72f439a5a77943fc8e9981b21 Mon Sep 17 00:00:00 2001 From: Simon Jakobi Date: Tue, 11 Nov 2025 19:05:52 +0100 Subject: [PATCH 10/17] Bangs and INLINE --- Data/HashMap/Internal.hs | 8 ++++---- Data/HashMap/Internal/Strict.hs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Data/HashMap/Internal.hs b/Data/HashMap/Internal.hs index 852ed144..81566be9 100644 --- a/Data/HashMap/Internal.hs +++ b/Data/HashMap/Internal.hs @@ -1262,7 +1262,7 @@ adjust# f k0 m0 = go h0 k0 0 m0 -- If it is @('Just' y)@, the key @k@ is bound to the new value @y@. update :: (Eq k, Hashable k) => (a -> Maybe a) -> k -> HashMap k a -> HashMap k a update f = Exts.inline alter (>>= f) -{-# INLINABLE update #-} +{-# INLINE update #-} -- | \(O(\log n)\) The expression @('alter' f k map)@ alters the value @x@ at @k@, or -- absence thereof. @@ -1273,11 +1273,11 @@ update f = Exts.inline alter (>>= f) -- 'lookup' k ('alter' f k m) = f ('lookup' k m) -- @ alter :: (Eq k, Hashable k) => (Maybe v -> Maybe v) -> k -> HashMap k v -> HashMap k v -alter f k = Exts.inline alter' f (hash k) k -{-# INLINABLE alter #-} +alter f !k = Exts.inline alter' f (hash k) k +{-# INLINE alter #-} alter' :: Eq k => (Maybe v -> Maybe v) -> Hash -> k -> HashMap k v -> HashMap k v -alter' f h0 k0 m0 = go_alter' h0 k0 0 m0 +alter' f !h0 !k0 = go_alter' h0 k0 0 where go_alter' !h !k !_ Empty = case f Nothing of Nothing -> Empty diff --git a/Data/HashMap/Internal/Strict.hs b/Data/HashMap/Internal/Strict.hs index c3fedea6..07fc4128 100644 --- a/Data/HashMap/Internal/Strict.hs +++ b/Data/HashMap/Internal/Strict.hs @@ -300,7 +300,7 @@ adjust f k0 m0 = go h0 k0 0 m0 -- If it is @('Just' y)@, the key @k@ is bound to the new value @y@. update :: (Eq k, Hashable k) => (a -> Maybe a) -> k -> HashMap k a -> HashMap k a update f = Exts.inline alter (>>= f) -{-# INLINABLE update #-} +{-# INLINE update #-} -- | \(O(\log n)\) The expression @('alter' f k map)@ alters the value @x@ at @k@, or -- absence thereof. @@ -314,7 +314,7 @@ alter :: (Eq k, Hashable k) => (Maybe v -> Maybe v) -> k -> HashMap k v -> HashM alter f = Exts.inline HM.alter $ \m -> case f m of Nothing -> Nothing Just !x -> Just x -{-# INLINABLE alter #-} +{-# INLINE alter #-} -- | \(O(\log n)\) The expression (@'alterF' f k map@) alters the value @x@ at -- @k@, or absence thereof. From b334d6a09805fcdfdeda216af0a3e59cd43b8047 Mon Sep 17 00:00:00 2001 From: Simon Jakobi Date: Tue, 11 Nov 2025 21:36:37 +0100 Subject: [PATCH 11/17] Update documentation for `two` --- Data/HashMap/Internal.hs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/Data/HashMap/Internal.hs b/Data/HashMap/Internal.hs index 81566be9..65f7c540 100644 --- a/Data/HashMap/Internal.hs +++ b/Data/HashMap/Internal.hs @@ -949,14 +949,11 @@ unsafeInsert k0 v0 m0 = runST (go h0 k0 v0 0 m0) | otherwise = go h k x s $ BitmapIndexed (mask hy s) (A.singleton t) {-# INLINABLE unsafeInsert #-} --- | Create a map from two key-value pairs which hashes don't collide. To --- enhance sharing, the second key-value pair is represented by the hash of its --- key and a singleton HashMap pairing its key with its value. --- --- Note: to avoid silly thunks, this function must be strict in the --- key. See issue #232. We don't need to force the HashMap argument --- because it's already in WHNF (having just been matched) and we --- just put it directly in an array. +-- | Create a map from a key-value pair and a 'Leaf' or 'Collision' node with +-- a different hash. +-- +-- It is the caller's responsibility to ensure that the 'HashMap' argument is +-- in WHNF. two :: Shift -> Hash -> k -> v -> Hash -> HashMap k v -> ST s (HashMap k v) two = go where From 1a643e6e588d444e8a2c6527926f5a955b9c1e38 Mon Sep 17 00:00:00 2001 From: Simon Jakobi Date: Wed, 12 Nov 2025 12:36:42 +0100 Subject: [PATCH 12/17] Clean up u-c.cabal --- unordered-containers.cabal | 1 - 1 file changed, 1 deletion(-) diff --git a/unordered-containers.cabal b/unordered-containers.cabal index afa0edd8..228eb53e 100644 --- a/unordered-containers.cabal +++ b/unordered-containers.cabal @@ -70,7 +70,6 @@ library BangPatterns ghc-options: -Wall -O2 -fwarn-tabs -ferror-spans - -- ghc-options: -Wall -fwarn-tabs -ferror-spans -- For dumping the generated code: -- ghc-options: -ddump-simpl -ddump-stg-final -ddump-cmm -ddump-asm -ddump-to-file From 65af25c964ba10733b1eda70300d5eb95b10e1ab Mon Sep 17 00:00:00 2001 From: Simon Jakobi Date: Wed, 12 Nov 2025 13:44:15 +0100 Subject: [PATCH 13/17] Bring back docs on `alterF` --- Data/HashMap/Internal.hs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Data/HashMap/Internal.hs b/Data/HashMap/Internal.hs index 65f7c540..89d79ace 100644 --- a/Data/HashMap/Internal.hs +++ b/Data/HashMap/Internal.hs @@ -1347,6 +1347,11 @@ alter' f !h0 !k0 = go_alter' h0 k0 0 Just v' -> runST $ two s h k v' hy t {-# INLINE alter' #-} +-- | \(O(\log n)\) The expression @('alterF' f k map)@ alters the value @x@ at +-- @k@, or absence thereof. +-- +-- 'alterF' can be used to insert, delete, or update a value in a map. +-- -- Note: 'alterF' is a flipped version of the 'at' combinator from -- . -- From 64212ab1414fb41a583c460b4f4621519932ceb0 Mon Sep 17 00:00:00 2001 From: Simon Jakobi Date: Wed, 12 Nov 2025 14:41:12 +0100 Subject: [PATCH 14/17] Extract `alterCollision` --- Data/HashMap/Internal.hs | 43 +++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/Data/HashMap/Internal.hs b/Data/HashMap/Internal.hs index 89d79ace..a47f8e84 100644 --- a/Data/HashMap/Internal.hs +++ b/Data/HashMap/Internal.hs @@ -1328,25 +1328,40 @@ alter' f !h0 !k0 = go_alter' h0 k0 0 _ -> Full (A.update ary i st') where i = index h s go_alter' h k s t@(Collision hy ls) - | h == hy = case indexOf k ls of - Just i -> do - case A.index# ls i of - (# L _ v #) -> - case f $ Just v of - Nothing - | A.length ls == 2 -> - case A.index# ls (otherOfOneOrZero i) of - (# l #) -> Leaf h l - | otherwise -> Collision hy (A.delete ls i) - Just v' -> Collision hy $ A.update ls i $ L k v' - Nothing -> case f Nothing of - Nothing -> t - Just v' -> Collision hy $ A.snoc ls $ L k v' + | h == hy = alterCollision f h k ls t | otherwise = case f Nothing of Nothing -> t Just v' -> runST $ two s h k v' hy t {-# INLINE alter' #-} +alterCollision + :: Eq k + => (Maybe v -> Maybe v) + -> Hash + -> k + -> A.Array (Leaf k v) + -> HashMap k v + -- ^ The original Collision node which will be re-used if the array is unchanged. + -- + -- It is the caller's responsibility to ensure that this argument is in WHNF. + -> HashMap k v +alterCollision f !h !k !ary orig = + case indexOf k ary of + Just i -> do + case A.index# ary i of + (# L _ v #) -> + case f $ Just v of + Nothing + | A.length ary == 2 -> + case A.index# ary (otherOfOneOrZero i) of + (# l #) -> Leaf h l + | otherwise -> Collision h (A.delete ary i) + Just v' -> Collision h $ A.update ary i $ L k v' + Nothing -> case f Nothing of + Nothing -> orig + Just v' -> Collision h $ A.snoc ary $ L k v' +{-# INLINABLE alterCollision #-} + -- | \(O(\log n)\) The expression @('alterF' f k map)@ alters the value @x@ at -- @k@, or absence thereof. -- From 303da194127b8e0699f16702bfeadb413bfaa536 Mon Sep 17 00:00:00 2001 From: Simon Jakobi Date: Wed, 12 Nov 2025 23:10:43 +0100 Subject: [PATCH 15/17] Revert "Change position of pointer-equality check" This reverts commit 2e7b1c492b30c15baab46b36f17a955ecf8b2e26. --- Data/HashMap/Internal.hs | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/Data/HashMap/Internal.hs b/Data/HashMap/Internal.hs index a47f8e84..9ef6528e 100644 --- a/Data/HashMap/Internal.hs +++ b/Data/HashMap/Internal.hs @@ -1300,17 +1300,20 @@ alter' f !h0 !k0 = go_alter' h0 k0 0 Just v' -> bitmapIndexedOrFull (b .|. m) $! A.insert ary i $! Leaf h $! L k v' | otherwise = case A.index# ary i of - (# !st #) -> case go_alter' h k (nextShift s) st of - Empty - | A.length ary == 2 - , (# l #) <- A.index# ary (otherOfOneOrZero i) - , isLeafOrCollision l - -> l - | otherwise -> BitmapIndexed (b .&. complement m) (A.delete ary i) - st' - | isLeafOrCollision st' && A.length ary == 1 -> st' - | st' `ptrEq` st -> t - | otherwise -> BitmapIndexed b (A.update ary i st') + (# !st #) -> do + let !st' = go_alter' h k (nextShift s) st + if st' `ptrEq` st + then t + else case st' of + Empty + | A.length ary == 2 + , (# l #) <- A.index# ary (otherOfOneOrZero i) + , isLeafOrCollision l + -> l + | otherwise + -> BitmapIndexed (b .&. complement m) (A.delete ary i) + l | isLeafOrCollision l && A.length ary == 1 -> l + _ -> BitmapIndexed b (A.update ary i st') where m = mask h s i = sparseIndex b m From 5fe4337c58ce96be6515c36f33aa0105d49e2f4f Mon Sep 17 00:00:00 2001 From: Simon Jakobi Date: Thu, 13 Nov 2025 00:13:16 +0100 Subject: [PATCH 16/17] alterCollision: Remove unnecessary comment --- Data/HashMap/Internal.hs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Data/HashMap/Internal.hs b/Data/HashMap/Internal.hs index 9ef6528e..30737e3d 100644 --- a/Data/HashMap/Internal.hs +++ b/Data/HashMap/Internal.hs @@ -1345,8 +1345,6 @@ alterCollision -> A.Array (Leaf k v) -> HashMap k v -- ^ The original Collision node which will be re-used if the array is unchanged. - -- - -- It is the caller's responsibility to ensure that this argument is in WHNF. -> HashMap k v alterCollision f !h !k !ary orig = case indexOf k ary of From 50e24901ee7964f37976e149e8b389aee6d7193f Mon Sep 17 00:00:00 2001 From: Simon Jakobi Date: Thu, 13 Nov 2025 00:13:51 +0100 Subject: [PATCH 17/17] alterCollision: Add pointer equality check --- Data/HashMap/Internal.hs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Data/HashMap/Internal.hs b/Data/HashMap/Internal.hs index 30737e3d..3fafba94 100644 --- a/Data/HashMap/Internal.hs +++ b/Data/HashMap/Internal.hs @@ -1357,7 +1357,9 @@ alterCollision f !h !k !ary orig = case A.index# ary (otherOfOneOrZero i) of (# l #) -> Leaf h l | otherwise -> Collision h (A.delete ary i) - Just v' -> Collision h $ A.update ary i $ L k v' + Just v' + | v' `ptrEq` v -> orig + | otherwise -> Collision h $ A.update ary i $ L k v' Nothing -> case f Nothing of Nothing -> orig Just v' -> Collision h $ A.snoc ary $ L k v'