{-# LANGUAGE BangPatterns #-} {-# LANGUAGE CPP #-} module Data.HashTable.Internal.Linear.Bucket ( Bucket, newBucketArray, newBucketSize, emptyWithSize, growBucketTo, snoc, size, lookup, delete, toList, fromList, mapM_, foldM, expandBucketArray, expandArray, nelemsAndOverheadInWords, bucketSplitSize ) where ------------------------------------------------------------------------------ import qualified Control.Monad import Control.Monad hiding (mapM_, foldM) import Control.Monad.ST import Data.Maybe (fromMaybe) import Data.HashTable.Internal.Array import Data.STRef import Prelude hiding (lookup, mapM_) ------------------------------------------------------------------------------ import Data.HashTable.Internal.UnsafeTricks #ifdef DEBUG import System.IO #endif type Bucket s k v = Key (Bucket_ s k v) ------------------------------------------------------------------------------ data Bucket_ s k v = Bucket { _bucketSize :: {-# UNPACK #-} !Int , _highwater :: {-# UNPACK #-} !(STRef s Int) , _keys :: {-# UNPACK #-} !(MutableArray s k) , _values :: {-# UNPACK #-} !(MutableArray s v) } ------------------------------------------------------------------------------ bucketSplitSize :: Int bucketSplitSize = 16 ------------------------------------------------------------------------------ newBucketArray :: Int -> ST s (MutableArray s (Bucket s k v)) newBucketArray k = newArray k emptyRecord ------------------------------------------------------------------------------ nelemsAndOverheadInWords :: Bucket s k v -> ST s (Int,Int) nelemsAndOverheadInWords bKey = do if (not $ keyIsEmpty bKey) then do !hw <- readSTRef hwRef let !w = sz - hw return (hw, constOverhead + 2*w) else return (0, 0) where constOverhead = 8 b = fromKey bKey sz = _bucketSize b hwRef = _highwater b ------------------------------------------------------------------------------ emptyWithSize :: Int -> ST s (Bucket s k v) emptyWithSize !sz = do !keys <- newArray sz undefined !values <- newArray sz undefined !ref <- newSTRef 0 return $ toKey $ Bucket sz ref keys values ------------------------------------------------------------------------------ newBucketSize :: Int newBucketSize = 4 ------------------------------------------------------------------------------ expandArray :: a -- ^ default value -> Int -- ^ new size -> Int -- ^ number of elements to copy -> MutableArray s a -- ^ old array -> ST s (MutableArray s a) expandArray def !sz !hw !arr = do newArr <- newArray sz def cp newArr where cp !newArr = go 0 where go !i | i >= hw = return newArr | otherwise = do readArray arr i >>= writeArray newArr i go (i+1) ------------------------------------------------------------------------------ expandBucketArray :: Int -> Int -> MutableArray s (Bucket s k v) -> ST s (MutableArray s (Bucket s k v)) expandBucketArray = expandArray emptyRecord ------------------------------------------------------------------------------ growBucketTo :: Int -> Bucket s k v -> ST s (Bucket s k v) growBucketTo !sz bk | keyIsEmpty bk = emptyWithSize sz | otherwise = do if osz >= sz then return bk else do hw <- readSTRef hwRef k' <- expandArray undefined sz hw keys v' <- expandArray undefined sz hw values return $ toKey $ Bucket sz hwRef k' v' where bucket = fromKey bk osz = _bucketSize bucket hwRef = _highwater bucket keys = _keys bucket values = _values bucket ------------------------------------------------------------------------------ {-# INLINE snoc #-} -- Just return == new bucket object snoc :: Bucket s k v -> k -> v -> ST s (Int, Maybe (Bucket s k v)) snoc bucket | keyIsEmpty bucket = mkNew | otherwise = snoc' (fromKey bucket) where mkNew !k !v = do debug "Bucket.snoc: mkNew" keys <- newArray newBucketSize undefined values <- newArray newBucketSize undefined writeArray keys 0 k writeArray values 0 v ref <- newSTRef 1 return (1, Just $ toKey $ Bucket newBucketSize ref keys values) snoc' (Bucket bsz hwRef keys values) !k !v = readSTRef hwRef >>= check where check !hw | hw < bsz = bump hw | otherwise = spill hw bump hw = do debug $ "Bucket.snoc: bumping hw, bsz=" ++ show bsz ++ ", hw=" ++ show hw writeArray keys hw k writeArray values hw v let !hw' = hw + 1 writeSTRef hwRef hw' debug "Bucket.snoc: finished" return (hw', Nothing) doublingThreshold = bucketSplitSize `div` 2 growFactor = 1.5 :: Double newSize z | z == 0 = newBucketSize | z < doublingThreshold = z * 2 | otherwise = ceiling $ growFactor * fromIntegral z spill !hw = do let sz = newSize bsz debug $ "Bucket.snoc: spilling, old size=" ++ show bsz ++ ", new size=" ++ show sz bk <- growBucketTo sz bucket debug "Bucket.snoc: spill finished, snoccing element" let (Bucket _ hwRef' keys' values') = fromKey bk let !hw' = hw+1 writeArray keys' hw k writeArray values' hw v writeSTRef hwRef' hw' return (hw', Just bk) ------------------------------------------------------------------------------ {-# INLINE size #-} size :: Bucket s k v -> ST s Int size b | keyIsEmpty b = return 0 | otherwise = readSTRef $ _highwater $ fromKey b ------------------------------------------------------------------------------ -- note: search in reverse order! We prefer recently snoc'd keys. lookup :: (Eq k) => Bucket s k v -> k -> ST s (Maybe v) lookup bucketKey !k | keyIsEmpty bucketKey = return Nothing | otherwise = lookup' $ fromKey bucketKey where lookup' (Bucket _ hwRef keys values) = do hw <- readSTRef hwRef go (hw-1) where go !i | i < 0 = return Nothing | otherwise = do k' <- readArray keys i if k == k' then do !v <- readArray values i return $! Just v else go (i-1) ------------------------------------------------------------------------------ {-# INLINE toList #-} toList :: Bucket s k v -> ST s [(k,v)] toList bucketKey | keyIsEmpty bucketKey = return [] | otherwise = toList' $ fromKey bucketKey where toList' (Bucket _ hwRef keys values) = do hw <- readSTRef hwRef go [] hw 0 where go !l !hw !i | i >= hw = return l | otherwise = do k <- readArray keys i v <- readArray values i go ((k,v):l) hw $ i+1 ------------------------------------------------------------------------------ -- fromList needs to reverse the input in order to make fromList . toList == id {-# INLINE fromList #-} fromList :: [(k,v)] -> ST s (Bucket s k v) fromList l = Control.Monad.foldM f emptyRecord (reverse l) where f bucket (k,v) = do (_,m) <- snoc bucket k v return $ fromMaybe bucket m ------------------------------------------------------------------------------ delete :: (Eq k) => Bucket s k v -> k -> ST s Bool delete bucketKey !k | keyIsEmpty bucketKey = do debug $ "Bucket.delete: empty bucket" return False | otherwise = do debug "Bucket.delete: start" del $ fromKey bucketKey where del (Bucket sz hwRef keys values) = do hw <- readSTRef hwRef debug $ "Bucket.delete: hw=" ++ show hw ++ ", sz=" ++ show sz go hw $ hw - 1 where go !hw !i | i < 0 = return False | otherwise = do k' <- readArray keys i if k == k' then do debug $ "found entry to delete at " ++ show i move (hw-1) i keys move (hw-1) i values let !hw' = hw-1 writeSTRef hwRef hw' return True else go hw (i-1) ------------------------------------------------------------------------------ {-# INLINE mapM_ #-} mapM_ :: ((k,v) -> ST s a) -> Bucket s k v -> ST s () mapM_ f bucketKey | keyIsEmpty bucketKey = do debug $ "Bucket.mapM_: bucket was empty" return () | otherwise = doMap $ fromKey bucketKey where doMap (Bucket sz hwRef keys values) = do hw <- readSTRef hwRef debug $ "Bucket.mapM_: hw was " ++ show hw ++ ", sz was " ++ show sz go hw 0 where go !hw !i | i >= hw = return () | otherwise = do k <- readArray keys i v <- readArray values i _ <- f (k,v) go hw $ i+1 ------------------------------------------------------------------------------ {-# INLINE foldM #-} foldM :: (a -> (k,v) -> ST s a) -> a -> Bucket s k v -> ST s a foldM f !seed0 bucketKey | keyIsEmpty bucketKey = return seed0 | otherwise = doMap $ fromKey bucketKey where doMap (Bucket _ hwRef keys values) = do hw <- readSTRef hwRef go hw seed0 0 where go !hw !seed !i | i >= hw = return seed | otherwise = do k <- readArray keys i v <- readArray values i seed' <- f seed (k,v) go hw seed' (i+1) ------------------------------------------------------------------------------ -- move i into j move :: Int -> Int -> MutableArray s a -> ST s () move i j arr | i == j = do debug $ "move " ++ show i ++ " into " ++ show j return () | otherwise = do debug $ "move " ++ show i ++ " into " ++ show j readArray arr i >>= writeArray arr j {-# INLINE debug #-} debug :: String -> ST s () #ifdef DEBUG debug s = unsafeIOToST $ do putStrLn s hFlush stdout #else #ifdef TESTSUITE debug !s = do let !_ = length s return $! () #else debug _ = return () #endif #endif