packages feed

stm-containers-0.1.3: executables/ConcurrentTransactionsBench.hs

import STMContainers.Prelude
import STMContainers.Transformers
import Criterion.Main
import Control.Monad.Free
import Control.Monad.Free.TH
import qualified Data.HashMap.Strict as UC
import qualified STMContainers.Map as SC
import qualified Control.Concurrent.Async as Async
import qualified System.Random.MWC.Monad as MWC
import qualified Focus
import qualified Data.Char as Char
import qualified Data.Text as Text
import qualified Data.Set as Set


-- * Custom data structures
-------------------------

type UCMap k v = TVar (UC.HashMap k (TVar v))


-- * Transactions 
-------------------------

data TransactionF k v n where
  Insert :: v -> k -> n -> TransactionF k v n
  Delete :: k -> n -> TransactionF k v n
  Lookup :: k -> n -> TransactionF k v n
  deriving (Functor, Show)

type Transaction k v = Free (TransactionF k v)

-- * Interpreters
-------------------------

type Interpreter m = 
  forall k v r. (Hashable k, Eq k) => m k v -> Transaction k v r -> STM r

ucInterpreter :: Interpreter UCMap
ucInterpreter m = 
  iterM $ \case
    Insert v k c -> 
      do
        mv <- readTVar m
        vt <- newTVar v
        writeTVar m $! UC.insert k vt mv
        c
    Delete k c -> 
      readTVar m >>= writeTVar m . UC.delete k >> c
    Lookup k c -> 
      readTVar m >>= mapM readTVar . UC.lookup k >> c

specializedSCInterpreter :: Interpreter SC.Map
specializedSCInterpreter m =
  iterM $ \case
    Insert v k c -> SC.insert v k m >> c
    Delete k c   -> SC.delete k m >> c
    Lookup k c   -> SC.lookup k m >> c

focusSCInterpreter :: Interpreter SC.Map
focusSCInterpreter m =
  iterM $ \case
    Insert v k c -> SC.focus (Focus.insertM v) k m >> c
    Delete k c   -> SC.focus Focus.deleteM k m >> c
    Lookup k c   -> SC.focus Focus.lookupM k m >> c


-- * Session and runners
-------------------------

-- | A list of transactions per thread.
type Session k v = [[Transaction k v ()]]

type SessionRunner = 
  forall k v. (Hashable k, Eq k) => Session k v -> IO ()

scSessionRunner :: Interpreter SC.Map -> SessionRunner
scSessionRunner interpreter threadTransactions = do
  m <- atomically $ SC.new
  void $ flip Async.mapConcurrently threadTransactions $ \actions -> do
    forM_ actions $ atomically . interpreter m

ucSessionRunner :: SessionRunner
ucSessionRunner threadTransactions = do
  m <- newTVarIO UC.empty
  void $ flip Async.mapConcurrently threadTransactions $ \actions -> do
    forM_ actions $ atomically . ucInterpreter m


-- * Generators
-------------------------

type Generator a = MWC.Rand IO a

-- |
-- Generate a list of transactions with a context of shared keys.
transactionsGroupGenerator :: Int -> Generator [Transaction Text.Text () ()]
transactionsGroupGenerator n =
  (flip evalStateT) Set.empty $ replicateM n transaction
  where
    transaction = do
      s <- size
      fmap sequence_ $ replicateM s action
      where
        size = 
          lift $ join $ weightedElementGenerator $
          [
            (1  , MWC.uniformR (8, 14)),
            (5  , MWC.uniformR (2, 7)),
            (10 , return 1)
          ]
        action =
          join $ lift $ weightedElementGenerator $
          [
            (1, delete),
            (5, insert),
            (20, lookup)
          ]
          where
            insert = do
              k <- key 10 1
              modify $ Set.insert k
              return $ liftF (Insert () k ())
            delete = do
              k <- key 1 10
              modify $ Set.delete k
              return $ liftF (Delete k ())
            lookup = do
              k <- key 1 5
              return $ liftF (Lookup k ())
            key unknownWeight knownWeight =
              join $ lift $ weightedElementGenerator $ 
              [
                (unknownWeight, unknown), 
                (knownWeight, known)
              ]
              where
                unknown = lift $ keyGenerator
                known = do
                  allKeys <- get
                  maybe unknown return =<< lift (setElementGenerator allKeys)
              
keyGenerator :: Generator Text.Text
keyGenerator = do
  l <- length
  s <- replicateM l char
  return $! Text.pack s
  where
    length = MWC.uniformR (7, 20)
    char = Char.chr <$> MWC.uniformR (Char.ord 'a', Char.ord 'z')

setElementGenerator :: Set.Set a -> Generator (Maybe a)
setElementGenerator set = do
  case Set.size set of
    0 -> return Nothing
    size -> Just . (flip Set.elemAt) set <$> MWC.uniformR (0, pred size)

elementGenerator :: [a] -> Generator a
elementGenerator = \case
  [] -> $bug "Empty list"
  l -> (!!) l <$> MWC.uniformR (0, (pred . length) l)

weightedElementGenerator :: [(Int, a)] -> Generator a
weightedElementGenerator = \case
  [] -> 
    $bug "Empty list"
  l -> 
    (flip pick) l <$> MWC.uniformR (1, total)
    where
      total = (sum . map fst) l
      pick n = \case
        (n', e) : t ->
          if n' >= n
            then e 
            else pick (n - n') t

-- * Utils
-------------------------

slices :: Int -> [a] -> [[a]]
slices size l =
  case splitAt size l of
    ([], _) -> []
    (a, b) -> a : slices size b


-- * Main
-------------------------

main = do
  -- Pregenerate the transactions:
  transactionsGroups <- 
    MWC.runWithCreate $ replicateM (maximum threadsNums) $
    transactionsGroupGenerator (transactionsNum `div` (maximum threadsNums))

  -- Run the benchmark:
  defaultMain $! flip map threadsNums $ \threadsNum -> 
    let
      session = 
        map concat $! 
        slices (length transactionsGroups `div` threadsNum) transactionsGroups
      in
        bench (shows threadsNum . showString "/" . shows (transactionsNum `div` threadsNum) $ "") $
          scSessionRunner specializedSCInterpreter session
  where
    threadsNums = [1, 2, 4, 6, 8, 12, 16, 32, 40, 52, 64, 80, 128]
    transactionsNum = 400000