swisstable-0.1.0.1: src/Data/HashTable/ST/Swiss.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Data.HashTable.ST.Swiss
( Table (..)
, new
, newSized
, insert'
, insert
, lookup'
, lookup
, delete'
, delete
, foldM
, mapM_
, analyze
, getSize
, mutateST
, mutate
) where
import Control.Monad (forM_, void, when)
import qualified Control.Monad as M
import Control.Monad.ST (RealWorld, ST)
import Data.Bits
import Data.Hashable
import Data.Primitive
import Data.Primitive.Array as A
import Data.Primitive.Ptr as PP
import Data.STRef
import Data.Word
import Foreign.C.Types
import GHC.Generics (Generic)
import GHC.IO (ioToST)
import Prelude hiding (lookup, mapM_)
-- todo: try foreign import prim
foreign import ccall unsafe "_elm_cmp_vec" cElmCmpVec :: Word8 -> Ptr Word8 -> Word32
foreign import ccall unsafe "_load_movemask" cLoadMovemask :: Ptr Word8 -> Word32
foreign import ccall unsafe "ffs" cFfs :: Word32 -> CInt
foreign import ccall unsafe "_elm_add_movemask" cElmAddMovemask :: Word8 -> Ptr Word8 -> Word32
newtype Table s k v = T (STRef s (Table_ s k v))
deriving (Generic)
-- todo: distibute STRef
data Table_ s k v = Table
{ elems :: {-# UNPACK #-} !(MutableArray s (k, v))
, ctrl :: {-# UNPACK #-} !(MutablePrimArray s Word8)
, size :: {-# UNPACK #-} !Int
, mask :: {-# UNPACK #-} !Int
, used :: {-# UNPACK #-} !(STRef s Int)
} deriving (Generic)
new :: ST s (Table s k v)
new = newSized 16
empty :: Word8
empty = 128
deleted :: Word8
deleted = 254
newSized :: Int -> ST s (Table s k v)
newSized n = do
when (n .&. (n - 1) /= 0) $ error "size should be power of 2"
es <- A.newArray n (error "impossible")
c <- newPinnedPrimArray (n + 32)
setPrimArray c 0 n empty
setPrimArray c n 32 deleted
u <- newSTRef 0
let t = Table es c (fromIntegral n) (fromIntegral n - 1) u
newRef t
newRef :: Table_ s k v -> ST s (Table s k v)
newRef = fmap T . newSTRef
{-# INLINE newRef #-}
readRef :: Table s k v -> ST s (Table_ s k v)
readRef (T ref) = readSTRef ref
{-# INLINE readRef #-}
writeRef :: Table s k v -> Table_ s k v -> ST s ()
writeRef (T ref) = writeSTRef ref
{-# INLINE writeRef #-}
insert' :: (Hashable k, Eq k) => (k -> Int) -> Table s k v -> k -> v -> ST s ()
insert' h m k v = do
mutateST' h m k (const $ pure (Just v, ()))
{-# INLINE insert' #-}
rawInsert :: (Hashable k, Eq k) => Int -> Table s k v -> k -> v -> ST s ()
rawInsert !h1' ref !k !v = do
m@Table{..} <- readRef ref
iterateCtrlIdx (f (mutablePrimArrayContents ctrl) size elems ctrl) size (mask .&. h1')
modifySTRef' used (+ 1)
checkOverflow m >>= \x -> when x $ grow ref
where
f !ptr !size !elems !ctrl !idx = do
let !pc = PP.advancePtr ptr idx
let !mask = cLoadMovemask pc
let !offset = cFfs mask - 1
let !idx' = idx + fromIntegral offset
if offset >= 0 && idx' < size then do
writeArray elems idx' (k, v)
writePrimArray ctrl idx' (h2 h1')
pure $ Just ()
else pure Nothing
{-# INLINE f #-}
{-# INLINE rawInsert #-}
lookup' :: forall k s a. (Hashable k, Eq k) => (k -> Int) -> Table s k a -> k -> ST s (Maybe a)
lookup' h !r !k = fmap fst <$> lookup'' (h1 h k) r k
{-# INLINE lookup' #-}
lookup'' :: forall k s a. (Hashable k, Eq k) => Int -> Table s k a -> k -> ST s (Maybe (a, Int))
lookup'' !h1' ref !k = do
Table{..} <- readRef ref
let !idx = mask .&. h1'
iterateCtrlIdx (lookCtrlAt (mutablePrimArrayContents ctrl) elems) size idx
where
!h2' = h2 h1'
lookBitmask es idx bidx = do
let idx' = idx + bidx - 1
(!k', v) <- readArray es idx'
pure $ if k == k' -- todo: opt(hashも保持?)
then Just (v, idx')
else Nothing
{-# INLINE lookBitmask #-}
lookCtrlAt !ptr !es !idx = do
let pc = PP.advancePtr ptr idx
let !mask = cElmCmpVec h2' pc
x <- iterateBitmaskSet (lookBitmask es idx) mask
case x of
Nothing
| cElmCmpVec 128 pc /= 0 -> pure (Just Nothing) -- found empty -- unlikely
| otherwise -> pure Nothing
_ -> pure (Just x)
{-# INLINE lookCtrlAt #-}
{-# INLINE lookup'' #-}
iterateCtrlIdx :: Monad m => (Int -> m (Maybe b)) -> Int -> Int -> m b
iterateCtrlIdx f !s !offset = go offset
where
go !idx = do
f idx >>= \case
Nothing ->
let !next = idx + 32
in if next > s then go 0 else go next
Just x -> pure x
{-# INLINE iterateCtrlIdx #-}
listBitmaskSet :: Word32 -> [CInt]
listBitmaskSet = map cFfs . iterate (\x -> x .&. (x - 1))
{-# INLINE listBitmaskSet #-}
iterateBitmaskSet :: Monad m => (Int -> m (Maybe a)) -> Word32 -> m (Maybe a)
iterateBitmaskSet !f !mask = do
let bitidxs = listBitmaskSet mask
go bitidxs
where
go (bidx:bidxs)
| bidx /= 0 = do
f (fromIntegral bidx) >>= \case
Nothing -> go bidxs
x -> pure x
| otherwise = pure Nothing
go _ = pure Nothing
{-# INLINE go #-}
{-# INLINE iterateBitmaskSet #-}
h1 :: Hashable k => (k -> Int) -> k -> Int
h1 = ($)
{-# INLINE h1 #-}
h2 :: Int -> Word8
h2 x = fromIntegral $ x .&. 127
{-# INLINE h2 #-}
-- delete :: (PrimMonad m, Hashable k, Eq k) => k -> Table (PrimState m) k v -> m ()
delete :: (Hashable k, Eq k) => Table s k v -> k -> ST s ()
delete = delete' hash
-- delete' :: (PrimMonad m, Hashable k, Eq k) => (k -> Int) -> k -> Table (PrimState m) k v -> m ()
delete' :: (Hashable k, Eq k) => (k -> Int) -> Table s k v -> k -> ST s ()
delete' hash' ref k = do
m <- readRef ref
let s = size m
let h1' = h1 hash' k
h2' = h2 h1'
let idx = (s - 1) .&. h1'
let es = elems m
let ct = ctrl m
let f'' offset = do
let pc = PP.advancePtr (mutablePrimArrayContents ct) offset
let mask = cElmCmpVec h2' pc
iterateBitmaskSet (readBM es offset) mask >>= \case
Nothing
| cElmCmpVec 128 pc /= 0 -> pure (Just Nothing)
| otherwise -> pure Nothing
x -> pure (Just x)
idx' <- iterateCtrlIdx f'' s idx
forM_ idx' $ deleteIdx m
where
readBM es offset bidx = do
let idx' = offset + bidx - 1
(k', _) <- readArray es idx'
pure $ if k == k'
then Just idx'
else Nothing
deleteIdx :: Table_ s k v
-> Int
-> ST s ()
deleteIdx m idx = do
writePrimArray (ctrl m) idx 254
modifySTRef (used m) (\x -> x - 1)
-- insert :: (PrimMonad m, Hashable k) => k -> v -> Table (PrimState m) k v -> m ()
insert :: (Hashable k, Eq k) => Table s k v -> k -> v -> ST s ()
insert = insert' hash
-- lookup :: (PrimMonad m, Hashable k, Show k, Eq k)
-- => k -> Table (PrimState m) k v -> m (Maybe v)
lookup :: (Hashable k, Eq k) => Table s k a -> k -> ST s (Maybe a)
lookup = lookup' hash
{-# INLINE lookup #-}
checkOverflow ::
(Hashable k) => Table_ s k v -> ST s Bool
checkOverflow t = do
u <- readSTRef (used t)
pure $ fromIntegral u / fromIntegral (size t) > maxLoad
{-# INLINE checkOverflow #-}
maxLoad :: Double
maxLoad = 0.8
grow :: (Hashable k, Eq k) => Table s k v -> ST s ()
grow ref = do
t <- readRef ref
let size' = size t * 2
t' <- newSized size'
mapM_ (f t') ref
writeRef ref =<< readRef t'
pure ()
where
f t (k, v) = insert t k v
mapM_ :: ((k, v) -> ST s a) -> Table s k v -> ST s ()
mapM_ f ref = do
t <- readRef ref
let idx = 0
void $ iterateCtrlIdx (h t) (size t) idx
where
g t idx bidx = do
let idx' = idx + bidx - 1
e <- readArray (elems t) idx'
void $ f e
pure Nothing
h t idx = do
let pc = PP.advancePtr (mutablePrimArrayContents (ctrl t)) idx
let mask = cElmAddMovemask 128 pc
r <- iterateBitmaskSet (g t idx) mask
if idx + 32 > size t then pure (Just Nothing) else pure r
foldM :: (a -> (k,v) -> ST s a) -> a -> Table s k v -> ST s a
foldM f seed0 ref = do
t <- readRef ref
foldCtrlM g seed0 t 0
where
g acc t idx (bidx:xs)
| bidx == 0 = pure acc
| otherwise = do
let idx' = idx + bidx - 1
e <- readArray (elems t) idx'
acc' <- f acc e
g acc' t idx xs
g _ _ _ _ = error "impossible"
foldCtrlM :: (a -> Table_ s k v -> Int -> [Int] -> ST s a) -> a -> Table_ s k v -> Int -> ST s a
foldCtrlM g acc t idx = do
let pc = PP.advancePtr (mutablePrimArrayContents (ctrl t)) idx
let mask = cElmAddMovemask 128 pc
acc' <- g acc t idx (map fromIntegral $ listBitmaskSet mask)
if idx + 32 > size t then pure acc' else foldCtrlM g acc' t (idx + 32)
_foldM :: (a -> (k,v) -> Int -> ST s a) -> a -> Table s k v -> ST s a
_foldM f seed0 ref = do
t <- readRef ref
foldCtrlM g seed0 t 0
where
g acc t idx (bidx:xs)
| bidx == 0 = pure acc
| otherwise = do
let idx' = idx + bidx - 1
e <- readArray (elems t) idx'
acc' <- f acc e idx'
g acc' t idx xs
g _ _ _ _ = error "impossible"
analyze :: (Hashable k, Show k) => (Table RealWorld k v -> ST RealWorld ())
analyze ref = do
t <- readRef ref
cs <- _foldM (f t) [] ref
u <- readSTRef (used t)
ioToST $ do
putStrLn $ "size: " <> show (size t)
putStrLn $ "used: " <> show u
putStrLn $ " " <> show (fromIntegral u / fromIntegral (size t) :: Double)
print $ "max diff: " <> show (maximum (fmap snd cs))
print $ "sum diff: " <> show (sum (fmap snd cs))
M.mapM_ print cs
where
f t acc (k, _) idx = do
let nidx = (size t - 1) .&. hash k
let d = if idx - nidx < 0 then idx - nidx + size t else idx - nidx
pure $ ((k, nidx, idx), d):acc
mutateST' :: (Eq k, Hashable k)
=> (k -> Int) -> Table s k v -> k -> (Maybe v -> ST s (Maybe v, a)) -> ST s a
mutateST' h ref k f = do
t <- readRef ref
let !h1' = h1 h k
lookup'' h1' ref k >>= \case
Just (v, idx) ->
f (Just v) >>= \case
(Just v', a) -> -- update
writeArray (elems t) idx (k, v') >> pure a
(Nothing, a) -> --delete
deleteIdx t idx >> pure a
Nothing ->
f Nothing >>= \case
(Just v', a) -> -- insert
rawInsert h1' ref k v' >> pure a
(Nothing, a) -> pure a
{-# INLINE mutateST' #-}
mutateST :: (Eq k, Hashable k)
=> Table s k v -> k -> (Maybe v -> ST s (Maybe v, a)) -> ST s a
mutateST = mutateST' hash
{-# INLINE mutateST #-}
mutate :: (Eq k, Hashable k) =>
Table s k v -> k -> (Maybe v -> (Maybe v, a)) -> ST s a
mutate ref !k !f = mutateST ref k (pure . f)
{-# INLINE mutate #-}
{-
試したい
右端で競合が発生した際に0に戻るのではなく、
予備領域を使い、予備領域が埋まったら拡張する。
-> unlikelyすぎて効果うすそう
-- make Data.HashTable.Class instance?
-> 両方に依存したインターフェス揃えるようlibrary作れば良い
-}
getSize :: Table s k v -> ST s Int
getSize = fmap size . readRef