bytestring-trie-0.2.7: dev/Bench/Regression.hs
{-# OPTIONS_GHC -Wall -fwarn-tabs #-}
{-# LANGUAGE CPP, BangPatterns #-}
----------------------------------------------------------------
-- ~ 2022.03.12
-- |
-- Module : Bench.Regression
-- Copyright : 2008--2022 wren romano
-- License : BSD-3-Clause
-- Maintainer : wren@cpan.org
-- Stability : provisional
-- Portability : portable (with bang-patterns)
--
----------------------------------------------------------------
module Bench.Regression (main) where
import Shared.BaseCompat
import qualified Data.Trie as T
-- import qualified Data.Trie.Internal as TI
import qualified Data.ByteString as S
#if MIN_VERSION_base(4,7,0)
-- [GHC 7.8.1]: "Data.Coerce" added to base.
import Data.Coerce (Coercible, coerce)
#endif
import qualified Data.Foldable as F
import qualified Criterion.Main as C
import qualified System.IO as Sys (withFile, IOMode(..), hIsEOF)
import qualified System.Environment as Sys (getProgName, getArgs, withArgs)
import qualified System.Exit as Sys (exitFailure)
----------------------------------------------------------------
-- | Read a file and insert each line into a trie with its (base-0)
-- line number.
readTrieFromFile :: FilePath -> IO (T.Trie Int)
readTrieFromFile file = Sys.withFile file Sys.ReadMode (go 0 T.empty)
where
go !i !t !h = do
b <- Sys.hIsEOF h
if b
then return t
else do
line <- S.hGetLine h
go (i+1) (T.insert line i t) h
-- TODO: maybe add some more CLI structure, for things like choosing
-- to use random inputs instead of file, or parameters of randomness,
-- etc.
main :: IO ()
main = do
args <- Sys.getArgs
case args of
[] -> do
prog <- Sys.getProgName
putStrLn "ERROR: Missing file argument"
putStrLn $ "Usage: " ++ prog ++ " FILE [criterionFlags]"
Sys.exitFailure
file:rest ->
Sys.withArgs rest $ C.defaultMain
[ C.env (readTrieFromFile file) $ \ t ->
bgroup_Foldable t
-- TODO: more benchmark suites here.
]
----------------------------------------------------------------
----------------------------------------------------------------
type BenchmarkE env = env -> C.Benchmark
type BenchmarkableE env = env -> C.Benchmarkable
bgroupE :: String -> [BenchmarkE env] -> BenchmarkE env
bgroupE name bs = \e -> C.bgroup name (($ e) <$> bs)
{-# INLINE bgroupE #-}
benchE :: String -> BenchmarkableE env -> BenchmarkE env
benchE name b = \e -> C.bench name (b e)
{-# INLINE benchE #-}
----------------------------------------------------------------
#if MIN_VERSION_base(4,7,0)
{-
-- | From "Data.Functor.Utils", but isn't exported. Is used heavily
-- by the default implementations, since they use so many newtype
-- wrappers.
(#.) :: Coercible b c => (b -> c) -> (a -> b) -> (a -> c)
(#.) _ = coerce
{-# INLINE (#.) #-}
-}
-- My own variant, ignoring the second argument instead.
(.#) :: Coercible a b => (b -> c) -> (a -> b) -> (a -> c)
(.#) f _ = coerce f
{-# INLINE (.#) #-}
#endif
----------------------------------------------------------------
-- Hopefully the @fmap Sum@ will be rewritten into 'coerce'. Though
-- we may want to do that explicitly...
bgroup_Foldable :: BenchmarkE (T.Trie Int)
bgroup_Foldable =
bgroupE "Foldable"
[ benchE "fold" $ C.nf
-- FIXME: the version using @(. fmap Sum)@ is monstrously slower
-- than the version using 'coerce'. On the one hand that makes
-- sense, but on the other hand it means we really should provide
-- rewrite rules for our Functor instance.
#if MIN_VERSION_base(4,7,0)
(F.fold .# fmap Sum)
#else
(F.fold . fmap Sum)
#endif
, benchE "foldMap" $ C.nf (F.foldMap Sum)
#if MIN_VERSION_base(4,13,0)
, benchE "foldMap'" $ C.nf (F.foldMap' Sum)
#endif
-- (2021.03.12): according to this benchmark, our hand-rolled 'foldr'
-- is indeed monstrously faster than the default implementation.
, benchE "foldr" $ C.nf (F.foldr (+) 0)
#if MIN_VERSION_base(4,6,0)
, benchE "foldr'" $ C.nf (F.foldr' (+) 0)
#endif
, benchE "foldl" $ C.nf (F.foldl (+) 0)
#if MIN_VERSION_base(4,6,0)
, benchE "foldl'" $ C.nf (F.foldl' (+) 0)
#endif
#if MIN_VERSION_base(4,8,0)
, benchE "length" $ C.nf F.length
{-
, benchE "maximum" $ C.nf F.maximum -- Must first ensure non-empty!
, benchE "minimum" $ C.nf F.minimum -- Must first ensure non-empty!
-}
#else
, benchE "size" $ C.nf T.size
#endif
]
----------------------------------------------------------------
----------------------------------------------------------- fin.