{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE CPP #-}
import Criterion.Main
#if __GLASGOW_HASKELL__ > 704
import Control.Concurrent (forkIO, setNumCapabilities, yield)
import GHC.Conc (getNumProcessors)
#else
import Control.Concurrent (forkIO, yield)
import GHC.Conc (getNumProcessors, setNumCapabilities)
#endif
import Control.Concurrent.Async (async)
import qualified Control.Concurrent.Async as Async
import Control.Concurrent.Chan
import Control.Concurrent.MVar
import Control.Concurrent.QSem
import Control.Concurrent.QSemN
import Control.Concurrent.STM
import Control.Concurrent.STM.TSem (newTSem, signalTSem, waitTSem)
import Control.DeepSeq (NFData(..))
import Control.Monad (replicateM, replicateM_, void, when)
import Data.Atomics.Counter
import Data.IORef
import Data.Function ((&))
instance NFData (IO a) where
rnf !_ = ()
benchSync :: (Int -> IO (IO (), IO ())) -> Int -> Benchmarkable
benchSync alloc i = perRunEnv setup $ \(start, wait) -> do
putMVar start ()
wait
where
setup = do
start <- newEmptyMVar
(signal, wait) <- alloc i
replicateM_ i . forkIO $ do
void $ readMVar start
signal
return (start, wait)
{-# INLINE benchSync #-}
benchSTM :: (Int -> IO (STM (), STM ())) -> Int -> Benchmarkable
benchSTM alloc = benchSync $ \i -> do
(signal, wait) <- alloc i
return (atomically signal, atomically wait)
{-# INLINE benchSTM #-}
generalSync :: (a -> Int -> Benchmarkable) -> String -> a -> Int -> Benchmark
generalSync build s alloc i = bench s $ build alloc i
{-# INLINE generalSync #-}
syncGeneral :: String -> (Int -> IO (IO (), IO ())) -> Int -> Benchmark
syncGeneral = generalSync benchSync
{-# INLINE syncGeneral #-}
syncSTM :: String -> (Int -> IO (STM (), STM ())) -> Int -> Benchmark
syncSTM = generalSync benchSTM
{-# INLINE syncSTM #-}
syncSingleWaitSTM :: String -> (IO (STM (), STM ())) -> Int -> Benchmark
syncSingleWaitSTM s alloc = bgroup s . sequence
[ bench "single transaction" . benchSTM singleTransaction
, bench "multi transaction" . benchSync multiTransaction
]
where
singleTransaction :: Int -> IO (STM (), STM ())
singleTransaction i = do
(signal, wait) <- alloc
return (signal, replicateM_ i wait)
{-# INLINE singleTransaction #-}
multiTransaction :: Int -> IO (IO (), IO ())
multiTransaction i = do
(signal, wait) <- alloc
return (atomically signal, replicateM_ i (atomically wait))
{-# INLINE multiTransaction #-}
{-# INLINE syncSingleWaitSTM #-}
syncAsync :: Int -> Benchmark
syncAsync = generalSync run "Async" ()
where
setup i = do
start <- newEmptyMVar
threads <- replicateM i . async $ readMVar start
return (start, mapM_ Async.wait threads)
{-# INLINE setup #-}
run () i = perRunEnv (setup i) $ \(start, wait) -> do
putMVar start()
wait
{-# INLINE run #-}
{-# INLINE syncAsync #-}
syncAtomicCounter :: Int -> Benchmark
syncAtomicCounter = syncGeneral "AtomicCounter" $ \i -> do
cnt <- newCounter 0
let spinLoop = do
n <- readCounter cnt
yield
when (n /= i) spinLoop
{-# INLINE spinLoop #-}
return (void (incrCounter 1 cnt), spinLoop)
{-# INLINE syncAtomicCounter #-}
syncChan :: Int -> Benchmark
syncChan = syncGeneral "Chan" $ \i -> do
chan <- newChan
return (writeChan chan (), replicateM_ i (readChan chan))
{-# INLINE syncChan #-}
syncIORef :: Int -> Benchmark
syncIORef = syncGeneral "IORef" $ \i -> do
ref <- newIORef 0
let spinLoop = do
n <- atomicModifyIORef' ref $ \n -> (n, n)
when (n /= i) spinLoop
{-# INLINE spinLoop #-}
return (atomicModifyIORef' ref (\n -> (n+1, ())), spinLoop)
{-# INLINE syncIORef #-}
syncMVar :: Int -> Benchmark
syncMVar = syncGeneral "MVar" $ \i -> do
mvar <- newEmptyMVar
return (putMVar mvar (), replicateM_ i (takeMVar mvar))
{-# INLINE syncMVar #-}
syncQSem :: Int -> Benchmark
syncQSem = syncGeneral "QSem" $ \i -> do
qsem <- newQSem 0
return (signalQSem qsem, replicateM_ i (waitQSem qsem))
{-# INLINE syncQSem #-}
syncQSemN :: Int -> Benchmark
syncQSemN = syncGeneral "QSemN" $ \i -> do
qsemn <- newQSemN 0
return (signalQSemN qsemn 1, waitQSemN qsemn i)
{-# INLINE syncQSemN #-}
syncTChan :: Int -> Benchmark
syncTChan = syncSingleWaitSTM "TChan" $ do
tchan <- newTChanIO
return (writeTChan tchan (), readTChan tchan)
{-# INLINE syncTChan #-}
syncTMVar :: Int -> Benchmark
syncTMVar = syncGeneral "TMVar" $ \i -> do
tmvar <- newEmptyTMVarIO
let signal = atomically $ putTMVar tmvar ()
wait = replicateM_ i . atomically $ takeTMVar tmvar
return (signal, wait)
{-# INLINE syncTMVar #-}
syncTQueue :: Int -> Benchmark
syncTQueue = syncSingleWaitSTM "TQueue" $ do
tqueue <- newTQueueIO
return (writeTQueue tqueue (), readTQueue tqueue)
{-# INLINE syncTQueue #-}
syncTSem :: Int -> Benchmark
syncTSem = syncSingleWaitSTM "TSem" $ do
tsem <- atomically $ newTSem 0
return (signalTSem tsem, waitTSem tsem)
{-# INLINE syncTSem #-}
syncTVar :: Int -> Benchmark
syncTVar = syncSTM "TVar" $ \i -> do
tvar <- newTVarIO 0
return (modifyTVar' tvar (+1), check . (==i) =<< readTVar tvar)
{-# INLINE syncTVar #-}
benchThreads :: Int -> Benchmark
benchThreads i = bgroup (show i ++ " threads") $ i & sequence
[ syncAsync
, syncAtomicCounter
, syncChan
, syncIORef
, syncMVar
, syncQSem
, syncQSemN
, syncTChan
, syncTMVar
, syncTVar
, syncTQueue
, syncTSem
]
main :: IO ()
main = do
getNumProcessors >>= setNumCapabilities
defaultMain $ map benchThreads [1, 2, 5, 10, 100, 1000, 10000]