acid-state-0.16.1.4: benchmarks/loading/Benchmark.hs
{-# LANGUAGE RecordWildCards #-}
import Benchmark.Prelude
import Criterion.Main
import qualified Data.Acid as Acid
import qualified Data.Acid.Memory as Memory
import qualified Benchmark.FileSystem as FS
import qualified Benchmark.Model as Model; import Benchmark.Model (Model)
import qualified System.Random as Random
main :: IO ()
main = defaultMain [benchmark defaultBenchmarkInterfaces [100,200,300,400]]
benchmark :: [BenchmarkInterface] -> [Int] -> Benchmark
benchmark bis sizes = env setupWorkingPath $ \ workingPath ->
bgroup "" $ map (benchmarksGroup bis workingPath) sizes
-- | An acid-state interface to be benchmarked.
data BenchmarkInterface = forall m .
BenchmarkInterface
{ benchName :: String
-- ^ Name of the interface, for use in constructing benchmarks.
, benchPersist :: Bool
-- ^ Does this interface actually persist data to disk? If
-- it doesn't, some benchmarks are not applicable.
, benchOpen :: FS.FilePath -> IO (Acid.AcidState m)
-- ^ Open an acid-state component with the given path. Note
-- that the type of the state is encapsulated within
-- 'BenchmarkInterface'.
, benchUpdate :: Acid.AcidState m -> [[Int]] -> IO ()
-- ^ Execute an 'insert' update against the acid-state.
, benchQuery :: Acid.AcidState m -> IO Int
-- ^ Execute a 'sumUp' query against the state.
}
memoryBenchmarkInterface :: BenchmarkInterface
memoryBenchmarkInterface =
BenchmarkInterface { benchName = "Memory"
, benchPersist = False
, benchOpen = const $ Memory.openMemoryState mempty
, benchUpdate = \ inst v -> Acid.update inst (Model.Insert v)
, benchQuery = \ inst -> Acid.query inst Model.SumUp
}
localBenchmarkInterface :: BenchmarkInterface
localBenchmarkInterface =
BenchmarkInterface { benchName = "Local"
, benchPersist = True
, benchOpen = \ p -> Acid.openLocalStateFrom (FS.encodeString p) mempty
, benchUpdate = \ inst v -> Acid.update inst (Model.Insert v)
, benchQuery = \ inst -> Acid.query inst Model.SumUp
}
defaultBenchmarkInterfaces :: [BenchmarkInterface]
defaultBenchmarkInterfaces = [memoryBenchmarkInterface, localBenchmarkInterface]
setupWorkingPath :: IO FS.FilePath
setupWorkingPath = do
workingPath <- do workingPath <- FS.getTemporaryDirectory
rndStr <- replicateM 16 $ Random.randomRIO ('a', 'z')
return $ workingPath <> "acid-state" <> "benchmarks" <> "loading" <> FS.decodeString rndStr
putStrLn $ "Working under the following temporary directory: " ++ FS.encodeString workingPath
FS.removeTreeIfExists workingPath
FS.createTree workingPath
return workingPath
benchmarksGroup :: [BenchmarkInterface] -> FS.FilePath -> Int -> Benchmark
benchmarksGroup bis workingPath size =
bgroup (show size)
[ bgroup (benchName bi) $
initializeBenchmarksGroup bi workingPath' size
: if benchPersist bi then [openCloseBenchmarksGroup bi workingPath' size] else []
| bi <- bis
]
where
workingPath' = workingPath <> FS.decodeString (show size)
-- | The Initialize benchmarks measure how long it takes to open an
-- empty 'AcidState' component, call 'initialize' to populate it with
-- data, and optionally checkpoint before closing.
initializeBenchmarksGroup :: BenchmarkInterface -> FS.FilePath -> Int -> Benchmark
initializeBenchmarksGroup bi workingPath size =
bgroup "Initialize"
[ bench "Without checkpoint" $ perRunEnv (prepareInitialize bi workingPath) $ \ _ ->
initializeClose bi workingPath size
, bench "With checkpoint" $ perRunEnv (prepareInitialize bi workingPath) $ \ _ ->
initializeCheckpointClose bi workingPath size
]
prepareInitialize :: BenchmarkInterface -> FS.FilePath -> IO ()
prepareInitialize bi workingPath =
when (benchPersist bi) $ do FS.removeTreeIfExists workingPath
FS.createTree workingPath
-- | The OpenClose benchmarks measure how long it takes to open an
-- existing on-disk 'AcidState' component (either from a checkpoint or
-- from a transaction log), optionally execute a query over the entire
-- state, then close. These benchmarks are not applicable if the
-- interface being benchmarked does not persist data.
openCloseBenchmarksGroup :: BenchmarkInterface -> FS.FilePath -> Int -> Benchmark
openCloseBenchmarksGroup bi workingPath size =
env (prepareOpenCloseBenchmarksGroup bi workingPath size) $ \ ~(logsInstancePath, checkpointInstancePath) -> bgroup "OpenClose"
[
bench "From Logs" $ nfIO $
openClose bi logsInstancePath
, bench "From Checkpoint" $ nfIO $
openClose bi checkpointInstancePath
, bench "From Logs (with query)" $ nfIO $
openQueryClose bi logsInstancePath
, bench "From Checkpoint (with query)" $ nfIO $
openQueryClose bi checkpointInstancePath
]
-- | Set up data on disk for the open/close benchmarks. This
-- initializes an instance, creates a copy of it (for restoring from
-- transaction logs), then checkpoints.
prepareOpenCloseBenchmarksGroup :: BenchmarkInterface -> FS.FilePath -> Int -> IO (FS.FilePath, FS.FilePath)
prepareOpenCloseBenchmarksGroup bi workingPath size = do
putStrLn $ "Preparing instances for size " ++ show size
let
logsInstancePath = workingPath <> "logs-instance"
checkpointInstancePath = workingPath <> "checkpoint-instance"
FS.createTree logsInstancePath
FS.createTree checkpointInstancePath
putStrLn "Initializing"
initialize bi checkpointInstancePath size $ \inst -> do
putStrLn "Copying"
FS.copy checkpointInstancePath logsInstancePath
FS.removeFile $ logsInstancePath <> "open.lock"
putStrLn "Checkpointing"
Acid.createCheckpoint inst
putStrLn "Closing"
Acid.closeAcidState inst
return (logsInstancePath, checkpointInstancePath)
initialize :: BenchmarkInterface -> FS.FilePath -> Int -> (forall m . Acid.AcidState m -> IO r) -> IO r
initialize BenchmarkInterface{..} p size k = do
inst <- benchOpen p
let values = replicate size $ replicate 100 $ replicate 100 1
mapM_ (benchUpdate inst) values
k inst
initializeClose :: BenchmarkInterface -> FS.FilePath -> Int -> IO ()
initializeClose bi p size = initialize bi p size Acid.closeAcidState
initializeCheckpointClose :: BenchmarkInterface -> FS.FilePath -> Int -> IO ()
initializeCheckpointClose bi p size =
initialize bi p size $ \ inst -> do
Acid.createCheckpoint inst
Acid.closeAcidState inst
openClose :: BenchmarkInterface -> FS.FilePath -> IO ()
openClose BenchmarkInterface{..} p = benchOpen p >>= Acid.closeAcidState
openQueryClose :: BenchmarkInterface -> FS.FilePath -> IO Int
openQueryClose BenchmarkInterface{..} p = do
inst <- benchOpen p
n <- benchQuery inst
Acid.closeAcidState inst
return n