Skip to content

Commit fc0fb88

Browse files
authored
Merge pull request #293 from IntersectMBO/arena-alloc
Implement arena allocator
2 parents cbf1c1e + 5f4cde5 commit fc0fb88

File tree

2 files changed

+132
-35
lines changed

2 files changed

+132
-35
lines changed

Diff for: bench/micro/Bench/Database/LSMTree/Internal/Lookup.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ benchLookups conf@Config{name} =
122122
FS.submitIO hasBlockIO ioops >>= \ioress ->
123123
pure (rkixs, ioops, ioress, arena)
124124
)
125-
(\(_, _, _, arena) -> closeArena arena) $ \ ~(rkixs, ioops, ioress, _) -> do
125+
(\(_, _, _, arena) -> closeArena arenaManager arena) $ \ ~(rkixs, ioops, ioress, _) -> do
126126
!_ <- intraPageLookups resolveV rs ks rkixs ioops ioress
127127
pure ()
128128
-- The whole shebang: lookup preparation, doing the IO, and then

Diff for: src/Data/Arena.hs

+131-34
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
{-# LANGUAGE CPP #-}
1+
{-# LANGUAGE CPP #-}
2+
{-# LANGUAGE LambdaCase #-}
3+
{-# LANGUAGE RecordWildCards #-}
24
module Data.Arena (
35
ArenaManager,
46
newArenaManager,
@@ -15,74 +17,169 @@ module Data.Arena (
1517
withUnmanagedArena,
1618
) where
1719

18-
import Control.DeepSeq (NFData (..), rwhnf)
20+
import Control.DeepSeq (NFData (..))
21+
import Control.Exception (assert)
1922
import Control.Monad.Primitive
23+
import Data.Bits (complement, popCount, (.&.))
2024
import Data.Primitive.ByteArray
2125
import Data.Primitive.MutVar
26+
import Data.Primitive.MVar
27+
import Data.Primitive.PrimVar
2228

2329
#ifdef NO_IGNORE_ASSERTS
24-
import Control.Monad (forM_)
2530
import Data.Word (Word8)
2631
#endif
2732

28-
data ArenaManager s = ArenaManager
33+
data ArenaManager s = ArenaManager (MutVar s [Arena s])
2934

3035
newArenaManager :: PrimMonad m => m (ArenaManager (PrimState m))
3136
newArenaManager = do
32-
_toUseTheConstraint <- newMutVar 'x'
33-
return ArenaManager
37+
m <- newMutVar []
38+
return $ ArenaManager m
3439

3540
-- | For use in bencmark environments
3641
instance NFData (ArenaManager s) where
37-
rnf _ = ()
42+
rnf (ArenaManager !_) = ()
3843

39-
-- TODO: this is debug implementation
40-
-- we retain all the allocated arrays,
41-
-- so we can scramble them at the end.
42-
data Arena s = Arena (MutVar s [MutableByteArray s])
44+
data Arena s = Arena
45+
{ curr :: !(MVar s (Block s)) -- current block, also acts as a lock
46+
, free :: !(MutVar s [Block s])
47+
, full :: !(MutVar s [Block s])
48+
}
49+
50+
data Block s = Block !(PrimVar s Int) !(MutableByteArray s)
4351

4452
instance NFData (Arena s) where
45-
rnf (Arena mvar) = rwhnf mvar
53+
rnf (Arena !_ !_ !_) = ()
4654

4755
type Size = Int
4856
type Offset = Int
4957
type Alignment = Int
5058

59+
blockSize :: Int
60+
blockSize = 0x100000
61+
62+
newBlock :: PrimMonad m => m (Block (PrimState m))
63+
newBlock = do
64+
off <- newPrimVar 0
65+
mba <- newAlignedPinnedByteArray blockSize 4096
66+
return (Block off mba)
67+
5168
withArena :: PrimMonad m => ArenaManager (PrimState m) -> (Arena (PrimState m) -> m a) -> m a
5269
withArena am f = do
53-
a <- newArena am
54-
x <- f a
55-
closeArena a
56-
pure x
70+
a <- newArena am
71+
x <- f a
72+
closeArena am a
73+
pure x
5774

5875
newArena :: PrimMonad m => ArenaManager (PrimState m) -> m (Arena (PrimState m))
59-
newArena _ = do
60-
mvar <- newMutVar []
61-
pure $! (Arena mvar)
76+
newArena (ArenaManager arenas) = do
77+
marena <- atomicModifyMutVar' arenas $ \case
78+
[] -> ([], Nothing)
79+
(x:xs) -> (xs, Just x)
6280

63-
closeArena :: PrimMonad m => Arena (PrimState m) -> m ()
64-
#ifdef NO_IGNORE_ASSERTS
65-
closeArena (Arena mvar) = do
66-
-- scramble the allocated bytearrays,
67-
-- they shouldn't be in use anymore!
68-
mbas <- readMutVar mvar
69-
forM_ mbas $ \mba -> do
70-
size <- getSizeofMutableByteArray mba
71-
setByteArray mba 0 size (0x77 :: Word8)
81+
case marena of
82+
Just arena -> return arena
83+
Nothing -> do
84+
curr <- newBlock >>= newMVar
85+
free <- newMutVar []
86+
full <- newMutVar []
87+
return Arena {..}
88+
89+
closeArena :: PrimMonad m => ArenaManager (PrimState m) -> Arena (PrimState m) -> m ()
90+
closeArena (ArenaManager arenas) arena = do
91+
scrambleArena arena
92+
93+
-- reset the arena to clear state
94+
resetArena arena
95+
96+
atomicModifyMutVar' arenas $ \xs -> (arena : xs, ())
97+
98+
99+
100+
scrambleArena :: PrimMonad m => Arena (PrimState m) -> m ()
101+
#ifndef NO_IGNORE_ASSERTS
102+
scrambleArena _ = return ()
72103
#else
73-
closeArena _ = pure ()
104+
scrambleArena Arena {..} = do
105+
readMVar curr >>= scrambleBlock
106+
readMutVar full >>= mapM_ scrambleBlock
107+
readMutVar free >>= mapM_ scrambleBlock
108+
109+
scrambleBlock :: PrimMonad m => Block (PrimState m) -> m ()
110+
scrambleBlock (Block _ mba) = do
111+
size <- getSizeofMutableByteArray mba
112+
setByteArray mba 0 size (0x77 :: Word8)
74113
#endif
75114

76-
-- | Create unmanaged arena
115+
-- | Reset arena, i.e. return used blocks to free list.
116+
resetArena :: PrimMonad m => Arena (PrimState m) -> m ()
117+
resetArena Arena {..} = do
118+
Block off mba <- takeMVar curr
119+
120+
-- reset current block
121+
writePrimVar off 0
122+
123+
-- move full block to free blocks.
124+
-- block's offset will be reset in 'newBlockWithFree'
125+
full' <- atomicModifyMutVar' full $ \xs -> ([], xs)
126+
atomicModifyMutVar' free $ \xs -> (full' <> xs, ())
127+
128+
putMVar curr $! Block off mba
129+
130+
-- | Create unmanaged arena.
77131
--
78132
-- Never use this in non-tests code.
79133
withUnmanagedArena :: PrimMonad m => (Arena (PrimState m) -> m a) -> m a
80134
withUnmanagedArena k = do
81135
mgr <- newArenaManager
82136
withArena mgr k
83137

138+
-- | Allocate a slice of mutable byte array from the arena.
84139
allocateFromArena :: PrimMonad m => Arena (PrimState m)-> Size -> Alignment -> m (Offset, MutableByteArray (PrimState m))
85-
allocateFromArena (Arena mvar) !size !alignment = do
86-
mba <- newAlignedPinnedByteArray size alignment
87-
atomicModifyMutVar' mvar $ \mbas -> (mba : mbas, ())
88-
return (0, mba)
140+
allocateFromArena !arena !size !alignment =
141+
assert (popCount alignment == 1) $ -- powers of 2
142+
assert (size <= blockSize) $ -- not too large allocations
143+
allocateFromArena' arena size alignment
144+
145+
-- TODO!? this is not async exception safe
146+
allocateFromArena' :: PrimMonad m => Arena (PrimState m)-> Size -> Alignment -> m (Offset, MutableByteArray (PrimState m))
147+
allocateFromArena' arena@Arena { .. } !size !alignment = do
148+
-- take current block, lock the arena
149+
curr'@(Block off mba) <- takeMVar curr
150+
151+
off' <- readPrimVar off
152+
let !ali = alignment - 1
153+
let !off'' = (off' + ali) .&. complement ali -- ceil towards next alignment
154+
let !end = off'' + size
155+
if end <= blockSize
156+
then do
157+
-- fits into current block:
158+
-- * update offset
159+
writePrimVar off end
160+
-- * release lock
161+
putMVar curr curr'
162+
-- * return data
163+
return (off'', mba)
164+
165+
else do
166+
-- doesn't fit into current block:
167+
-- * move current block into full
168+
atomicModifyMutVar' full (\xs -> (curr' : xs, ()))
169+
-- * allocate new block
170+
new <- newBlockWithFree free
171+
-- * set new block as current, release the lock
172+
putMVar curr new
173+
-- * go again
174+
allocateFromArena' arena size alignment
175+
176+
-- | Allocate new block, possibly taking it from a free list
177+
newBlockWithFree :: PrimMonad m => MutVar (PrimState m) [Block (PrimState m)] -> m (Block (PrimState m))
178+
newBlockWithFree free = do
179+
free' <- readMutVar free
180+
case free' of
181+
[] -> newBlock
182+
x@(Block off _):xs -> do
183+
writePrimVar off 0
184+
writeMutVar free xs
185+
return x

0 commit comments

Comments
 (0)