Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize IntMap.alter using unboxed sums. #523

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions Data/IntMap/Internal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
#if __GLASGOW_HASKELL__ >= 708
{-# LANGUAGE TypeFamilies #-}
#endif
#if __GLASGOW_HASKELL__ >= 802
{-# LANGUAGE UnboxedSums #-}
{-# LANGUAGE UnboxedTuples #-}
#endif

{-# OPTIONS_HADDOCK not-home #-}

Expand Down Expand Up @@ -311,6 +315,9 @@ import Prelude hiding (lookup, map, filter, foldr, foldl, null)
import Data.IntSet.Internal (Key)
import qualified Data.IntSet.Internal as IntSet
import Utils.Containers.Internal.BitUtil
#if __GLASGOW_HASKELL__ >= 802
import Utils.Containers.Internal.PtrEquality (ptrEq)
#endif
import Utils.Containers.Internal.StrictFold
import Utils.Containers.Internal.StrictPair

Expand Down Expand Up @@ -937,6 +944,48 @@ updateLookupWithKey _ _ Nil = (Nothing,Nil)
-- | /O(min(n,W))/. The expression (@'alter' f k map@) alters the value @x@ at @k@, or absence thereof.
-- 'alter' can be used to insert, delete, or update a value in an 'IntMap'.
-- In short : @'lookup' k ('alter' f k m) = f ('lookup' k m)@.

#if __GLASGOW_HASKELL__ >= 802
alter :: (Maybe a -> Maybe a) -> Key -> IntMap a -> IntMap a
alter f !k t = case alter# f k t of
(# (# #) | #) -> t
(# | t' #) -> t'
{-# INLINE alter #-}


-- Internal implementation which keeps track of whether or not the intmap was
-- modified using an unboxed sum (Maybe).
--
-- If no modifications are made to the map (# (# #) | #) is returned, otherwise
-- (# | newMap #) is returned.
alter# :: (Maybe a -> Maybe a) -> Key -> IntMap a -> (# (# #) | IntMap a #)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if you make the function have type Maybe# a -> Maybe# a, where Maybe# is the unboxed version of Maybe? Couldn't we often avoid the Maybe allocation that way? Benchmark!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That function is user defined (passed to alter) so at some point you would have to do the conversion from Maybe to Maybe#. As far as I know coerce wouldn't work for f because they have different representations (Maybe and Maybe# that is), but there may be another way that I'm unaware of. Did you have a specific approach in mind?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's a decent bet that the function we're passed will be small enough to inline. So calling alter# with

\ p -> fromMaybe (f (toMaybe p))

(essentially) should usually avoid any actual Maybes. Or so I imagine.

alter# f !k t@(Bin p m l r)
| nomatch k p m = case f Nothing of
Nothing -> (# (# #) | #)
Just x -> (# | link k (Tip k x) p t #)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you're being strict enough with your return values.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean something like the following?

let !t' = link k (Tip k x) p t in (# | t' #)

Or something else?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but see the link below for a clearer way.

| zero k m = case alter# f k l of
(# (# #) | #) -> (# (# #) | #)
(# | l' #) -> (# | binCheckLeft p m l' r #)

| otherwise = case alter# f k r of
(# (# #) | #) -> (# (# #) | #)
(# | r' #) -> (# | binCheckRight p m l r' #)

alter# f k t@(Tip ky y)
| k==ky = case f (Just y) of
Just x -> if x `ptrEq` y
then (# (# #) | #)
else (# | Tip ky x #)
Nothing -> (# | Nil #)
| otherwise = case f Nothing of
Just x -> (# | link k (Tip k x) ky t #)
Nothing -> (# (# #) | #)
alter# f k Nil = case f Nothing of
Just x -> (# | Tip k x #)
Nothing -> (# (# #) | #)
{-# INLINABLE alter# #-}

#else
alter :: (Maybe a -> Maybe a) -> Key -> IntMap a -> IntMap a
alter f !k t@(Bin p m l r)
| nomatch k p m = case f Nothing of
Expand All @@ -954,6 +1003,7 @@ alter f k t@(Tip ky y)
alter f k Nil = case f Nothing of
Just x -> Tip k x
Nothing -> Nil
#endif

-- | /O(log n)/. The expression (@'alterF' f k map@) alters the value @x@ at
-- @k@, or absence thereof. 'alterF' can be used to inspect, insert, delete,
Expand Down
49 changes: 49 additions & 0 deletions Data/IntMap/Strict.hs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
#if !defined(TESTING) && __GLASGOW_HASKELL__ >= 703
{-# LANGUAGE Trustworthy #-}
#endif
#if __GLASGOW_HASKELL__ >= 802
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE UnboxedSums #-}
{-# LANGUAGE UnboxedTuples #-}
#endif

#include "containers.h"

Expand Down Expand Up @@ -303,6 +308,9 @@ import Data.IntMap.Internal
import Data.IntMap.Internal.DeprecatedDebug (showTree, showTreeWith)
import qualified Data.IntSet.Internal as IntSet
import Utils.Containers.Internal.BitUtil
#if __GLASGOW_HASKELL__ >= 802
import Utils.Containers.Internal.PtrEquality (ptrEq)
#endif
import Utils.Containers.Internal.StrictFold
import Utils.Containers.Internal.StrictPair
#if !MIN_VERSION_base(4,8,0)
Expand Down Expand Up @@ -559,6 +567,46 @@ updateLookupWithKey f0 !k0 t0 = toPair $ go f0 k0 t0
-- | /O(min(n,W))/. The expression (@'alter' f k map@) alters the value @x@ at @k@, or absence thereof.
-- 'alter' can be used to insert, delete, or update a value in an 'IntMap'.
-- In short : @'lookup' k ('alter' f k m) = f ('lookup' k m)@.
#if __GLASGOW_HASKELL__ >= 802
alter :: (Maybe a -> Maybe a) -> Key -> IntMap a -> IntMap a
alter f !k t = case alter# f k t of
(# (# #) | #) -> t
(# | t' #) -> t'
{-# INLINE alter #-}


-- Internal implementation which keeps track of whether or not the intmap was
-- modified using an unboxed sum (Maybe).
--
-- If no modifications are made to the map (# (# #) | #) is returned, otherwise
-- (# | newMap #) is returned.
alter# :: (Maybe a -> Maybe a) -> Key -> IntMap a -> (# (# #) | IntMap a #)
alter# f !k t@(Bin p m l r)
| nomatch k p m = case f Nothing of
Nothing -> (# (# #) | #)
Just !x -> (# | link k (Tip k x) p t #)
| zero k m = case alter# f k l of
(# (# #) | #) -> (# (# #) | #)
(# | l' #) -> (# | binCheckLeft p m l' r #)

| otherwise = case alter# f k r of
(# (# #) | #) -> (# (# #) | #)
(# | r' #) -> (# | binCheckRight p m l r' #)

alter# f k t@(Tip ky y)
| k==ky = case f (Just y) of
Just x -> if x `ptrEq` y
then (# (# #) | #)
else (# | Tip ky x #)
Nothing -> (# | Nil #)
| otherwise = case f Nothing of
Just !x -> (# | link k (Tip k x) ky t #)
Nothing -> (# (# #) | #)
alter# f k Nil = case f Nothing of
Just !x -> (# | Tip k x #)
Nothing -> (# (# #) | #)

#else
alter :: (Maybe a -> Maybe a) -> Key -> IntMap a -> IntMap a
alter f !k t =
case t of
Expand All @@ -578,6 +626,7 @@ alter f !k t =
Nil -> case f Nothing of
Just !x -> Tip k x
Nothing -> Nil
#endif

-- | /O(log n)/. The expression (@'alterF' f k map@) alters the value @x@ at
-- @k@, or absence thereof. 'alterF' can be used to inspect, insert, delete,
Expand Down