packages feed

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.