Skip to content

Commit 16955a3

Browse files
committed
Take #1: use mutable hashtables + RWLock.
1 parent 5abc34e commit 16955a3

6 files changed

Lines changed: 95 additions & 107 deletions

File tree

package.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ dependencies:
2424
- mtl
2525
- containers
2626
- unordered-containers
27+
- hashtables
2728
- concurrent-extra
29+
- stm-containers
2830
- psqueues
2931
- aeson
3032
- yaml

src/AI/AlphaBeta.hs

Lines changed: 28 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -91,34 +91,33 @@ getPossibleMoves :: GameRules rules => AICacheHandle rules eval -> Side -> Board
9191
getPossibleMoves handle side board = Monitoring.timed "ai.possible_moves.duration" $ do
9292
let rules = aichRules handle
9393
Monitoring.increment "ai.possible_moves.calls"
94-
return $ possibleMoves rules side board
95-
-- (result, hit) <- liftIO $ atomically $ do
96-
-- memo <- readTVar (aichPossibleMoves handle)
97-
-- let rules = aichRules handle
98-
-- let bc = boardCounts board
99-
-- bk = boardKey board
100-
-- let moves = possibleMoves rules side board
101-
-- case lookupBoardMap (bc, bk) memo of
102-
-- Nothing -> do
103-
-- let value = case side of
104-
-- First -> (Just moves, Nothing)
105-
-- Second -> (Nothing, Just moves)
106-
-- let memo' = putBoardMap board value memo
107-
-- writeTVar (aichPossibleMoves handle) memo'
108-
-- return (moves, False)
109-
-- Just (Just cachedMoves, _) | side == First -> return (cachedMoves, True)
110-
-- Just (_, Just cachedMoves) | side == Second -> return (cachedMoves, True)
111-
-- Just (mbMoves1, mbMoves2) -> do
112-
-- let value
113-
-- | side == First = (Just moves, mbMoves2)
114-
-- | otherwise = (mbMoves1, Just moves)
115-
-- let memo' = putBoardMap board value memo
116-
-- writeTVar (aichPossibleMoves handle) memo'
117-
-- return (moves, False)
118-
-- if hit
119-
-- then Monitoring.increment "ai.possible_moves.hit"
120-
-- else Monitoring.increment "ai.possible_moves.miss"
121-
-- return result
94+
-- return $ possibleMoves rules side board
95+
(result, hit) <- liftIO $ do
96+
let memo = aichPossibleMoves handle
97+
let rules = aichRules handle
98+
let bc = boardCounts board
99+
bk = boardKey board
100+
let moves = possibleMoves rules side board
101+
mbItem <- lookupBoardMap memo board
102+
case mbItem of
103+
Nothing -> do
104+
let value = case side of
105+
First -> (Just moves, Nothing)
106+
Second -> (Nothing, Just moves)
107+
putBoardMap memo board value
108+
return (moves, False)
109+
Just (Just cachedMoves, _) | side == First -> return (cachedMoves, True)
110+
Just (_, Just cachedMoves) | side == Second -> return (cachedMoves, True)
111+
Just (mbMoves1, mbMoves2) -> do
112+
let value
113+
| side == First = (Just moves, mbMoves2)
114+
| otherwise = (mbMoves1, Just moves)
115+
putBoardMap memo board value
116+
return (moves, False)
117+
if hit
118+
then Monitoring.increment "ai.possible_moves.hit"
119+
else Monitoring.increment "ai.possible_moves.miss"
120+
return result
122121

123122
-- | General driver / controller for Alpha-Beta prunning algorithm.
124123
-- This method is responsible in running scoreAB method on all possible moves
@@ -352,7 +351,7 @@ runAI ai@(AlphaBeta params rules eval) handle side board = do
352351
scoreMoves :: [PossibleMove] -> DepthParams -> (Score, Score) -> Checkers [Either Error (PossibleMove, Score)]
353352
scoreMoves moves dp (alpha, beta) = do
354353
let var = aichData handle
355-
AICache _ processor _ <- liftIO $ atomically $ readTVar var
354+
let processor = aichProcessor handle
356355
let inputs = [
357356
ScoreMoveInput {
358357
smiAi = ai,

src/AI/AlphaBeta/Cache.hs

Lines changed: 17 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ loadAiCache scoreMove (AlphaBeta params rules eval) = do
4747
let getKey input = pmResult (smiMove input)
4848
aiCfg <- asks (gcAiConfig . csConfig)
4949
processor <- runProcessor (aiThreads aiCfg) getKey scoreMove
50-
cache <- liftIO $ atomically $ newTVar $ AICache False processor emptyBoardMap
50+
cache <- liftIO newTBoardMap
5151
cachePath <- do
5252
home <- liftIO $ getEnv "HOME"
5353
let directory = home </> ".cache" </> "hcheckers" </> rulesName rules </> "ai.cache"
@@ -100,10 +100,11 @@ loadAiCache scoreMove (AlphaBeta params rules eval) = do
100100
fhHandle = fd
101101
}
102102
counts <- liftIO $ atomically $ newTVar $ BoardCounts 50 50 50 50
103-
moves <- liftIO $ atomically $ newTVar emptyBoardMap
103+
moves <- liftIO newTBoardMap
104104
let handle = AICacheHandle {
105105
aichRules = rules,
106106
aichData = cache,
107+
aichProcessor = processor,
107108
aichPossibleMoves = moves,
108109
aichWriteQueue = writeQueue,
109110
aichCleanupQueue = cleanupQueue,
@@ -138,22 +139,6 @@ cacheDumper rules params handle = do
138139

139140
cacheCleaner :: AICacheHandle rules eval -> Checkers ()
140141
cacheCleaner handle = forever $ do
141-
delta <- liftIO $ atomically $ do
142-
aic <- readTVar (aichData handle)
143-
currentCounts <- readTVar (aichCurrentCounts handle)
144-
let cache = aicData aic
145-
let oldSize = boardMapSize cache
146-
let cache' = M.filterWithKey biggerCounts cache
147-
biggerCounts bc _ =
148-
(bcFirstMen bc + bcFirstKings bc + bcSecondMen bc + bcSecondKings bc) <=
149-
(bcFirstMen currentCounts + bcFirstKings currentCounts + bcSecondMen currentCounts + bcSecondKings currentCounts)
150-
aic' = aic {aicData = cache'}
151-
newSize = boardMapSize cache'
152-
delta = oldSize - newSize
153-
writeTVar (aichData handle) aic'
154-
return delta
155-
$info "cleanup: cleaned {} records" (Single delta)
156-
157142
liftIO $ threadDelay $ 30 * 1000 * 1000
158143

159144
normalize :: BoardSize -> (BoardCounts,BoardKey,Side) -> (BoardCounts,BoardKey,Side)
@@ -173,7 +158,7 @@ lookupAiCache params board depth side handle = do
173158
-- let fixSign = if side' == side then id else negate
174159
let bc = boardCounts board
175160
bk = boardKey board
176-
(cachedScore, cachedStats) <- lookupMemory (bc, bk) side
161+
(cachedScore, cachedStats) <- lookupMemory board side
177162
case (cachedScore, cachedStats) of
178163
(Just result, Nothing) -> do
179164
Monitoring.increment "cache.hit.memory"
@@ -225,11 +210,12 @@ lookupAiCache params board depth side handle = do
225210
| statsCount s < 10 = Nothing
226211
| otherwise = Just s
227212

228-
lookupMemory :: (BoardCounts, BoardKey) -> Side -> Checkers (Maybe CacheItemSide, Maybe Stats)
229-
lookupMemory (bc, bk) side = Monitoring.timed "cache.lookup.memory" $ do
213+
lookupMemory :: Board -> Side -> Checkers (Maybe CacheItemSide, Maybe Stats)
214+
lookupMemory board side = Monitoring.timed "cache.lookup.memory" $ do
230215
cfg <- asks (gcAiConfig . csConfig)
231-
AICache _ _ cache <- liftIO $ atomically $ readTVar (aichData handle)
232-
case lookupBoardMap (bc,bk) cache of
216+
let cache = aichData handle
217+
mbItem <- liftIO $ lookupBoardMap cache board
218+
case mbItem of
233219
Nothing -> return (Nothing, Nothing)
234220
Just (PerBoardData {..}) -> do
235221
let depths = [dpLast depth .. dpLast depth + aiUseCacheMaxDepthPlus cfg] ++
@@ -269,20 +255,16 @@ putAiCache' params board depth side sideItem handle = do
269255
now <- liftIO $ getTime Monotonic
270256
Monitoring.increment "cache.records.put"
271257
fileCacheEnabled <- asks (aiStoreCache . gcAiConfig . csConfig)
272-
liftIO $ atomically $ do
273-
aic <- readTVar (aichData handle)
274-
let item = case side of
275-
First -> CacheItem {ciFirst = Just sideItem, ciSecond = Nothing}
276-
Second -> CacheItem {ciFirst = Nothing, ciSecond = Just sideItem}
277-
278-
init = PerBoardData (M.singleton (dpLast depth) item) Nothing
258+
let cache = aichData handle
259+
let item = case side of
260+
First -> CacheItem {ciFirst = Just sideItem, ciSecond = Nothing}
261+
Second -> CacheItem {ciFirst = Nothing, ciSecond = Just sideItem}
279262

280-
newAicData = putBoardMapWith (<>) (bc,bk) init (aicData aic)
281-
aic' = aic {aicDirty = True, aicData = newAicData}
263+
init = PerBoardData (M.singleton (dpLast depth) item) Nothing
282264

283-
Just perBoard = lookupBoardMap (bc,bk) newAicData
265+
liftIO $ putBoardMapWith cache (<>) board init
284266

285-
writeTVar (aichData handle) aic'
267+
liftIO $ atomically $ do
286268
when (fileCacheEnabled && needWriteFile) $
287269
putWriteQueue (aichWriteQueue handle) (board, depth, side, sideItem)
288270
putCleanupQueue (aichCleanupQueue handle) (bc, bk) now
@@ -292,3 +274,4 @@ putAiCache :: GameRules rules => AlphaBetaParams -> Board -> DepthParams -> Side
292274
putAiCache params board depth side score moves handle = do
293275
let sideItem = CacheItemSide {cisScore = score}
294276
putAiCache' params board depth side sideItem handle
277+

src/AI/AlphaBeta/Types.hs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ instance Monoid PerBoardData where
136136
instance Binary PerBoardData
137137
instance Store PerBoardData
138138

139-
type AIData = BoardMap PerBoardData
139+
type AIData = TBoardMap PerBoardData
140140

141141
type StorageKey = (DepthParams, BoardKey)
142142

@@ -154,12 +154,6 @@ data ScoreMoveInput rules eval = ScoreMoveInput {
154154
, smiBeta :: Score
155155
}
156156

157-
data AICache rules eval = AICache {
158-
aicDirty :: Bool
159-
, aicProcessor :: Processor [MoveAction] (ScoreMoveInput rules eval) (PossibleMove, Score)
160-
, aicData :: AIData
161-
}
162-
163157
type QueueKey = (BoardCounts, BoardKey)
164158

165159
type IndexBlockNumber = Word32
@@ -168,14 +162,15 @@ type DataBlockNumber = Word32
168162
data FileType = IndexFile | DataFile
169163
deriving (Eq, Show)
170164

171-
type MovesMemo = BoardMap (Maybe [PossibleMove], Maybe [PossibleMove])
165+
type MovesMemo = TBoardMap (Maybe [PossibleMove], Maybe [PossibleMove])
172166

173167
-- | Handle to the instance of AI storage
174168
-- and related structures
175169
data AICacheHandle rules eval = AICacheHandle {
176170
aichRules :: rules
177-
, aichData :: TVar (AICache rules eval)
178-
, aichPossibleMoves :: TVar MovesMemo
171+
, aichData :: AIData
172+
, aichProcessor :: Processor [MoveAction] (ScoreMoveInput rules eval) (PossibleMove, Score)
173+
, aichPossibleMoves :: MovesMemo
179174
, aichWriteQueue :: WriteQueue
180175
, aichCleanupQueue :: CleanupQueue
181176
, aichCurrentCounts :: TVar BoardCounts

src/Core/BoardMap.hs

Lines changed: 38 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22
module Core.BoardMap where
33

44
import Control.Monad
5+
import Control.Exception (bracket_)
56
import qualified Data.Map as M
67
import qualified Data.HashMap.Strict as H
78
import qualified Data.IntMap.Strict as IM
89
import qualified Data.IntSet as IS
910
import Data.Array.IArray as A
1011
import Data.Hashable
12+
import qualified Data.HashTable.IO as HT
13+
import qualified Control.Concurrent.ReadWriteLock as RWL
1114
import Data.Store
1215
import Data.Word
1316
import Text.Printf
@@ -78,39 +81,41 @@ removeBoardKey a (Piece Man Second) bk = bk {bkSecondMen = deleteLabelSet (aLabe
7881
removeBoardKey a (Piece King First) bk = bk {bkFirstKings = deleteLabelSet (aLabel a) (bkFirstKings bk)}
7982
removeBoardKey a (Piece King Second) bk = bk {bkSecondKings = deleteLabelSet (aLabel a) (bkSecondKings bk)}
8083

81-
putBoardMap :: Board -> a -> BoardMap a -> BoardMap a
82-
putBoardMap board x bmap =
83-
M.unionWith H.union bmap init
84-
where
85-
init = M.singleton (boardCounts board) $ H.singleton (boardKey board) x
86-
87-
putBoardMapWith :: (a -> a -> a) -> (BoardCounts,BoardKey) -> a -> BoardMap a -> BoardMap a
88-
putBoardMapWith plus (bc,bk) x bmap =
89-
M.unionWith (H.unionWith plus) bmap init
90-
where
91-
init = M.singleton bc $ H.singleton bk x
92-
93-
lookupBoardMap :: (BoardCounts,BoardKey) -> BoardMap a -> Maybe a
94-
lookupBoardMap (bc,bk) bmap =
95-
H.lookup bk =<< M.lookup bc bmap
96-
97-
singleBoardMap :: Board -> a -> BoardMap a
98-
singleBoardMap board x =
99-
M.singleton (boardCounts board) $ H.singleton (boardKey board) x
100-
101-
emptyBoardMap :: BoardMap a
102-
emptyBoardMap = M.empty
103-
104-
deleteBoardMap :: BoardCounts -> BoardKey -> BoardMap a -> BoardMap a
105-
deleteBoardMap bc bk bm =
106-
let del m = let m' = H.delete bk m
107-
in if H.null m'
108-
then Nothing
109-
else Just m'
110-
in M.update del bc bm
111-
112-
boardMapSize :: BoardMap a -> Int
113-
boardMapSize bmap = sum $ map H.size $ M.elems bmap
84+
newTBoardMap :: IO (TBoardMap a)
85+
newTBoardMap = do
86+
lock <- RWL.new
87+
m <- HT.newSized (100*1000)
88+
return (lock, m)
89+
90+
putBoardMap :: TBoardMap a -> Board -> a -> IO ()
91+
putBoardMap (lock, bmap) board value =
92+
bracket_
93+
(RWL.acquireWrite lock)
94+
(RWL.releaseWrite lock)
95+
(HT.insert bmap (boardCounts board, boardKey board) value)
96+
97+
putBoardMapWith :: TBoardMap a -> (a -> a -> a) -> Board -> a -> IO ()
98+
putBoardMapWith (lock, bmap) plus board value =
99+
bracket_
100+
(RWL.acquireWrite lock)
101+
(RWL.releaseWrite lock)
102+
(do mbOld <- HT.lookup bmap (boardCounts board, boardKey board)
103+
case mbOld of
104+
Nothing -> HT.insert bmap (boardCounts board, boardKey board) value
105+
Just old -> HT.insert bmap (boardCounts board, boardKey board) (plus old value)
106+
)
107+
108+
lookupBoardMap :: TBoardMap a -> Board -> IO (Maybe a)
109+
lookupBoardMap (lock, bmap) board =
110+
bracket_
111+
(RWL.acquireRead lock)
112+
(RWL.releaseRead lock)
113+
(HT.lookup bmap (boardCounts board, boardKey board))
114+
115+
boardMapSize :: TBoardMap a -> IO Int
116+
boardMapSize (_, bmap) = do
117+
list <- HT.toList bmap
118+
return $ length list
114119

115120
------------------
116121

src/Core/Types.hs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import Control.Monad.Except
1616
import Control.Monad.Metrics as Metrics
1717
import Control.Concurrent
1818
import Control.Concurrent.STM
19+
import qualified Control.Concurrent.ReadWriteLock as RWL
1920
import Data.List
2021
import Data.Array.Unboxed
2122
import qualified Data.Map as M
@@ -25,6 +26,7 @@ import qualified Data.Text as T
2526
import qualified Data.Text.Lazy as TL
2627
import qualified Data.Text.Lazy.Builder.Int as Builder
2728
import qualified Data.HashMap.Strict as H
29+
import qualified Data.HashTable.IO as HT
2830
import Data.Text.Format.Heavy
2931
import Data.Dynamic
3032
import Data.Aeson (Value)
@@ -193,7 +195,9 @@ data BoardKey = BoardKey {
193195

194196
instance Binary BoardKey
195197

196-
type BoardMap a = M.Map BoardCounts (H.HashMap BoardKey a)
198+
type BoardMap a = HT.CuckooHashTable (BoardCounts, BoardKey) a
199+
200+
type TBoardMap a = (RWL.RWLock, BoardMap a)
197201

198202
-- | Direction on the board.
199203
-- For example, B2 is at UpRight of A1.

0 commit comments

Comments
 (0)