File: BenchMain.hs

package info (click to toggle)
haskell-atomic-counter 0.1.2.4-2
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 144 kB
  • sloc: haskell: 490; makefile: 6
file content (150 lines) | stat: -rw-r--r-- 5,710 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
----------------------------------------------------------------------------
-- |
-- Module      :  BenchMain
-- Copyright   :  (c) Sergey Vinokurov 2022
-- License     :  Apache-2.0 (see LICENSE)
-- Maintainer  :  serg.foo@gmail.com
----------------------------------------------------------------------------

{-# LANGUAGE BangPatterns        #-}
{-# LANGUAGE CPP                 #-}
{-# LANGUAGE MagicHash           #-}
{-# LANGUAGE NamedFieldPuns      #-}
{-# LANGUAGE UnboxedTuples       #-}

module BenchMain (main) where

import Control.Concurrent
import Control.Concurrent.STM
import Control.Monad
import Data.IORef
import Data.Primitive.Types
import Data.Semigroup
import GHC.Exts
import GHC.IO
import Test.QuickCheck
import Test.Tasty hiding (defaultMain)
import Test.Tasty.Bench
import Test.Tasty.Patterns.Printer
import qualified Test.Tasty.QuickCheck as QC

import qualified Control.Concurrent.Counter.Lifted.IO as C

import TestUtils

#if MIN_VERSION_base(4, 16, 0)
readAddr :: Addr# -> Word
readAddr addr = W# (indexWordOffAddr# addr 0#)

withAddr :: (Addr# -> IO a) -> IO a
withAddr f = IO $ \s1 -> case newPinnedByteArray# (sizeOf# (undefined :: Word)) s1 of
  (# s2, mbarr #) ->
    let !addr = mutableByteArrayContents# mbarr in
      case writeWordOffAddr# addr 0# 0## s2 of
        s3 -> case unIO (f addr) s3 of
          (# s4, res #) -> case touch# mbarr s4 of
            s5 -> (# s5, res #)

incrementAddr :: Addr# -> Int -> IO ()
incrementAddr addr (I# delta) = IO $ \s1 -> case fetchAddWordAddr# addr (int2Word# delta) s1 of
  (# s2, _ #) -> (# s2, () #)
#endif

incrementIORef :: IORef Int -> Int -> IO ()
incrementIORef !x !delta = atomicModifyIORef' x (\old -> (old + delta, ()))

incrementIORefInconsistent :: IORef Int -> Int -> IO ()
incrementIORefInconsistent !x !delta = do
  n <- readIORef x
  writeIORef x $! n + delta

incrementMVar :: MVar Int -> Int -> IO ()
incrementMVar !x !delta = do
  !n <- takeMVar x
  putMVar x $! n + delta

incrementTMVar :: TMVar Int -> Int -> IO ()
incrementTMVar !x !delta = atomically $ do
  !n <- takeTMVar x
  putTMVar x $! n + delta

incrementTVar :: TVar Int -> Int -> IO ()
incrementTVar !x !delta = atomically $ modifyTVar' x (+ delta)

incrementCounter :: C.Counter -> Int -> IO ()
incrementCounter !x !delta = void (C.add x delta)


main :: IO ()
main = do
  let tests =
        [ localOption (QC.QuickCheckTests 10000) $
          QC.testProperty "Correctness" $
            \(Threads ts) -> ioProperty $ do

              a <- spawnAndCall ts (newIORef 0)   (\ref t -> runThread t (\_ -> pure ()) (incrementIORef ref)) >>= readIORef
              b <- spawnAndCall ts (newMVar 0)    (\ref t -> runThread t (\_ -> pure ()) (incrementMVar ref)) >>= takeMVar
              c <- spawnAndCall ts (newTMVarIO 0) (\ref t -> runThread t (\_ -> pure ()) (incrementTMVar ref)) >>= atomically . takeTMVar
              d <- spawnAndCall ts (newTVarIO 0)  (\ref t -> runThread t (\_ -> pure ()) (incrementTVar ref)) >>= atomically . readTVar
              -- e <- spawnAndCall ts (newIORef 0)   (\ref t -> runThread t (incrementIORefInconsistent ref *> sleep delay)) >>= readIORef
#if MIN_VERSION_base(4, 16, 0)
              f <- withAddr $ \addr -> do
                spawnAndCall ts (pure ()) (\() t -> runThread t (\_ -> pure ()) (incrementAddr addr))
                evaluate (readAddr addr)
#endif

              g <- spawnAndCall ts (C.new 0) (\ref t -> runThread t (\_ -> pure ()) (incrementCounter ref)) >>= C.get

              let Sum expected =
                    foldMap (\Thread{tIncrement, tIterations} -> Sum $ tIncrement * unIterations tIterations) ts

              pure $
                a === expected .&&.
                b === expected .&&.
                c === expected .&&.
                d === expected .&&.
#if MIN_VERSION_base(4, 16, 0)
                fromIntegral f === expected .&&.
#endif
                g === expected
        ]

  let benchmarks = map (mapLeafBenchmarks addCompare)
        [ bgroup ("Read/write contention with " ++ show (unIterations n) ++ " iterations and " ++ show (length threads) ++ " threads")
          [ bench counterBenchName $
            whnfIO (spawnAndCall threads (C.new 0)      (\ref _ -> callN n (incrementCounter ref 1)))
          , bench "IORef inconsistent" $
            whnfIO (spawnAndCall threads (newIORef 0)   (\ref _ -> callN n (incrementIORefInconsistent ref 1)))
          , bench "IORef atomic" $
            whnfIO (spawnAndCall threads (newIORef 0)   (\ref _ -> callN n (incrementIORef ref 1)))
          , bench "MVar" $
            whnfIO (spawnAndCall threads (newMVar 0)    (\ref _ -> callN n (incrementMVar ref 1)))
          , bench "TMVar" $
            whnfIO (spawnAndCall threads (newTMVarIO 0) (\ref _ -> callN n (incrementTMVar ref 1)))
          , bench "TVar" $
            whnfIO (spawnAndCall threads (newTVarIO 0)  (\ref _ -> callN n (incrementTVar ref 1)))
#if MIN_VERSION_base(4, 16, 0)
          , bench "Addr" $
            whnfIO $ withAddr $ \addr -> do
              spawnAndCall
                threads
                (pure ())
                (\() _ -> callN n (incrementAddr addr 1))
              evaluate (readAddr addr)
#endif
          ]
        | maxThreads <- [1, 2, 4, 6, 8, 12, 16, 20, 32, 64, 128]
        , let threads = [1..maxThreads]
        , n <- [Iterations 10, Iterations 100, Iterations 1000, Iterations 10000]
        ]

  defaultMain $ tests ++ benchmarks

counterBenchName :: String
counterBenchName = "Counter"

addCompare :: [String] -> Benchmark -> Benchmark
addCompare (name : path)
  | name /= counterBenchName
  = bcompare (printAwkExpr (locateBenchmark (counterBenchName : path)))
addCompare _ = id