streamly-0.7.2: benchmark/lib/Streamly/Benchmark/Common.hs
{-# LANGUAGE CPP #-}
#if __GLASGOW_HASKELL__ >= 800
{-# OPTIONS_GHC -Wno-orphans #-}
#endif
-- |
-- Module : Streamly.Benchmark.Common
-- Copyright : (c) 2019 Composewell Technologies
--
-- License : BSD3
-- Maintainer : streamly@composewell.com
module Streamly.Benchmark.Common
( parseCLIOpts
, benchIOSink1
, benchPure
, benchPureSink1
, benchFold
, benchIOSrc1
, benchPureSrc
, mkString
, mkList
, mkListString
, defaultStreamSize
, limitStreamSize
)
where
import Control.DeepSeq (NFData(..))
import Control.Exception (evaluate)
import Control.Monad (when)
import Data.Functor.Identity (Identity, runIdentity)
import Data.List (scanl')
import Data.Maybe (catMaybes)
import System.Console.GetOpt
(OptDescr(..), ArgDescr(..), ArgOrder(..), getOpt')
import System.Environment (getArgs, lookupEnv, setEnv)
import Text.Read (readMaybe)
import System.Random (randomRIO)
import qualified Streamly.Prelude as S
import Streamly
import Gauge
-------------------------------------------------------------------------------
-- Benchmarking utilities
-------------------------------------------------------------------------------
#if !MIN_VERSION_deepseq(1,4,3)
instance NFData Ordering where rnf = (`seq` ())
#endif
-- XXX once we convert all the functions to use this we can rename this to
-- benchIOSink
{-# INLINE benchIOSink1 #-}
benchIOSink1 :: NFData b => String -> (Int -> IO b) -> Benchmark
benchIOSink1 name f = bench name $ nfIO $ randomRIO (1,1) >>= f
{-# INLINE benchIOSrc1 #-}
benchIOSrc1 :: String -> (Int -> IO ()) -> Benchmark
benchIOSrc1 name f = bench name $ nfIO $ randomRIO (1,1) >>= f
-- We need a monadic bind here to make sure that the function f does not get
-- completely optimized out by the compiler in some cases.
{-# INLINE benchFold #-}
benchFold :: NFData b
=> String -> (t IO Int -> IO b) -> (Int -> t IO Int) -> Benchmark
benchFold name f src = bench name $ nfIO $ randomRIO (1,1) >>= f . src
{-# INLINE benchPure #-}
benchPure :: NFData b => String -> (Int -> a) -> (a -> b) -> Benchmark
benchPure name src f = bench name $ nfIO $ randomRIO (1,1) >>= return . f . src
-- XXX once we convert all the functions to use this we can rename this to
-- benchPureSink
{-# INLINE benchPureSink1 #-}
benchPureSink1 :: NFData b => String -> (Int -> Identity b) -> Benchmark
benchPureSink1 name f =
bench name $ nfIO $ randomRIO (1,1) >>= return . runIdentity . f
{-# INLINE benchPureSrc #-}
benchPureSrc :: String -> (Int -> SerialT Identity a) -> Benchmark
benchPureSrc name src = benchPure name src (runIdentity . S.drain)
-------------------------------------------------------------------------------
-- String/List generation for read instances
-------------------------------------------------------------------------------
{-# INLINABLE mkString #-}
mkString :: Int -> String
mkString value = "fromList [1" ++ concat (replicate value ",1") ++ "]"
{-# INLINABLE mkListString #-}
mkListString :: Int -> String
mkListString value = "[1" ++ concat (replicate value ",1") ++ "]"
{-# INLINABLE mkList #-}
mkList :: Int -> [Int]
mkList value = [1..value]
-------------------------------------------------------------------------------
-- Stream size
-------------------------------------------------------------------------------
defaultStreamSize :: Int
defaultStreamSize = 100000
limitStreamSize :: Int -> IO Int
limitStreamSize value = do
let val = min value defaultStreamSize
when (val /= value) $
putStrLn $ "Limiting stream size to "
++ show defaultStreamSize
++ " for non O(1) space operations"
return val
-------------------------------------------------------------------------------
-- Parse custom CLI options
-------------------------------------------------------------------------------
data BenchOpts = StreamSize Int deriving Show
getStreamSize :: String -> Int
getStreamSize size =
case (readMaybe size :: Maybe Int) of
Just x -> x
Nothing -> error "Stream size must be numeric"
options :: [OptDescr BenchOpts]
options =
[
Option [] ["stream-size"] (ReqArg getSize "COUNT") "Stream element count"
]
where
getSize = StreamSize . getStreamSize
deleteOptArgs
:: (Maybe String, Maybe String) -- (prev, yielded)
-> String
-> (Maybe String, Maybe String)
deleteOptArgs (Nothing, _) opt =
if opt == "--stream-size"
then (Just opt, Nothing)
else (Just opt, Just opt)
deleteOptArgs (Just prev, _) opt =
if opt == "--stream-size" || prev == "--stream-size"
then (Just opt, Nothing)
else (Just opt, Just opt)
parseCLIOpts :: Int -> IO (Int, Config, [String])
parseCLIOpts defStreamSize = do
args <- getArgs
-- Parse custom options
let (opts, _, _, errs) = getOpt' Permute options args
when (not $ null errs) $ error $ concat errs
(streamSize, args') <-
case opts of
StreamSize x : _ -> do
-- When using the gauge "--measure-with" option we need to make
-- sure that we pass the stream size to child process forked by
-- gauge. So we use this env var for that purpose.
setEnv "STREAM_SIZE" (show x)
-- Hack! remove the option and its argument from args
-- getOpt should have a way to return the unconsumed args in
-- correct order.
newArgs <-
evaluate
$ catMaybes
$ map snd
$ scanl' deleteOptArgs (Nothing, Nothing) args
return (x, newArgs)
_ -> do
r <- lookupEnv "STREAM_SIZE"
case r of
Just x -> do
s <- evaluate $ getStreamSize x
return (s, args)
Nothing -> return (defStreamSize, args)
-- Parse gauge options
let config = defaultConfig
{ timeLimit = Just 1
, minDuration = 0
, includeFirstIter = streamSize > defStreamSize
}
let (cfg, benches) = parseWith config args'
streamSize `seq` return (streamSize, cfg, benches)