-- | -- Module : Stream.Expand -- Copyright : (c) 2018 Composewell Technologies -- License : BSD-3-Clause -- Maintainer : streamly@composewell.com {-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TupleSections #-} #ifdef USE_PRELUDE {-# OPTIONS_GHC -Wno-deprecations #-} #endif #ifdef __HADDOCK_VERSION__ #undef INSPECTION #endif #ifdef INSPECTION {-# LANGUAGE TemplateHaskell #-} {-# OPTIONS_GHC -fplugin Test.Inspection.Plugin #-} #endif module Stream.Expand (benchmarks) where #ifdef INSPECTION import GHC.Types (SPEC(..)) import Test.Inspection import qualified Streamly.Internal.Data.Stream as D #endif import qualified Stream.Common as Common import qualified Streamly.Internal.Data.Unfold as UF #ifdef USE_PRELUDE import qualified Streamly.Internal.Data.Stream.IsStream as S import qualified Streamly.Internal.Data.Stream.IsStream as StreamK import Streamly.Benchmark.Prelude ( sourceFoldMapM, sourceFoldMapWith, sourceFoldMapWithM , sourceFoldMapWithStream, concatFoldableWith, concatForFoldableWith) #else import qualified Streamly.Internal.Data.Stream as S #ifdef USE_STREAMK import Streamly.Internal.Data.Stream (Stream) import Streamly.Internal.Data.StreamK (StreamK, CrossStreamK) import qualified Control.Applicative as AP import qualified Streamly.Internal.Data.Fold as Fold import qualified Streamly.Internal.Data.StreamK as StreamK #else import qualified Streamly.Internal.Data.Stream as StreamK #endif #endif import Test.Tasty.Bench import Stream.Common import Streamly.Benchmark.Common import Prelude hiding (concatMap) ------------------------------------------------------------------------------- -- Iteration/looping utilities ------------------------------------------------------------------------------- #ifdef USE_STREAMK {-# INLINE iterateN #-} iterateN :: (Int -> a -> a) -> a -> Int -> a iterateN g initial count = f count initial where f (0 :: Int) x = x f i x = f (i - 1) (g i x) -- Iterate a transformation over a singleton stream {-# INLINE iterateSingleton #-} iterateSingleton :: Applicative m => (Int -> CrossStreamK m Int -> CrossStreamK m Int) -> Int -> Int -> Stream m Int iterateSingleton g count n = toStream $ StreamK.unCross $ iterateN g (StreamK.mkCross (StreamK.fromPure n)) count #endif {- -- XXX need to check why this is slower than the explicit recursion above, even -- if the above code is written in a foldr like head recursive way. We also -- need to try this with foldlM' once #150 is fixed. -- However, it is perhaps best to keep the iteration benchmarks independent of -- foldrM and any related fusion issues. {-# INLINE _iterateSingleton #-} _iterateSingleton :: Monad m => (Int -> Stream m Int -> Stream m Int) -> Int -> Int -> Stream m Int _iterateSingleton g value n = S.foldrM g (return n) $ sourceIntFromTo value n -} ------------------------------------------------------------------------------- -- Multi-Stream ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- -- Appending ------------------------------------------------------------------------------- {-# INLINE serial2 #-} serial2 :: Int -> Int -> IO () serial2 count n = drain $ toStream $ Common.append (fromStream $ sourceUnfoldrM count n) (fromStream $ sourceUnfoldrM count (n + 1)) {-# INLINE serial4 #-} serial4 :: Int -> Int -> IO () serial4 count n = drain $ toStream $ Common.append (Common.append (fromStream $ sourceUnfoldrM count n) (fromStream $ sourceUnfoldrM count (n + 1))) (Common.append (fromStream $ sourceUnfoldrM count (n + 2)) (fromStream $ sourceUnfoldrM count (n + 3))) o_1_space_joining :: Int -> [Benchmark] o_1_space_joining value = [ bgroup "joining" [ benchIOSrc1 "serial (2,x/2)" (serial2 (value `div` 2)) , benchIOSrc1 "serial (2,2,x/4)" (serial4 (value `div` 4)) ] ] ------------------------------------------------------------------------------- -- Concat Foldable containers ------------------------------------------------------------------------------- #ifdef USE_PRELUDE o_1_space_concatFoldable :: Int -> [Benchmark] o_1_space_concatFoldable value = [ bgroup "concat-foldable" [ benchIOSrc "foldMapWith (<>) (List)" (sourceFoldMapWith value) , benchIOSrc "foldMapWith (<>) (Stream)" (sourceFoldMapWithStream value) , benchIOSrc "foldMapWithM (<>) (List)" (sourceFoldMapWithM value) , benchIOSrc "S.concatFoldableWith (<>) (List)" (concatFoldableWith value) , benchIOSrc "S.concatForFoldableWith (<>) (List)" (concatForFoldableWith value) , benchIOSrc "foldMapM (List)" (sourceFoldMapM value) ] ] #endif ------------------------------------------------------------------------------- -- Concat ------------------------------------------------------------------------------- -- concatMap unfoldrM/unfoldrM {-# INLINE concatMap #-} concatMap :: Int -> Int -> Int -> IO () concatMap outer inner n = drain $ toStream $ StreamK.concatMap (\_ -> fromStream $ sourceUnfoldrM inner n) (fromStream $ sourceUnfoldrM outer n) #ifndef USE_STREAMK {-# INLINE concatMapM #-} concatMapM :: Int -> Int -> Int -> IO () concatMapM outer inner n = drain $ S.concatMapM (\_ -> return $ sourceUnfoldrM inner n) (sourceUnfoldrM outer n) #ifdef INSPECTION inspect $ hasNoTypeClasses 'concatMap inspect $ 'concatMap `hasNoType` ''SPEC #endif #endif -- concatMap unfoldr/unfoldr {-# INLINE concatMapPure #-} concatMapPure :: Int -> Int -> Int -> IO () concatMapPure outer inner n = drain $ toStream $ StreamK.concatMap (\_ -> fromStream $ sourceUnfoldr inner n) (fromStream $ sourceUnfoldr outer n) #ifdef INSPECTION #if __GLASGOW_HASKELL__ >= 906 inspect $ hasNoTypeClassesExcept 'concatMapPure [''Applicative] #else inspect $ hasNoTypeClasses 'concatMapPure #endif inspect $ 'concatMapPure `hasNoType` ''SPEC #endif -- concatMap replicate/unfoldrM {-# INLINE concatMapRepl #-} concatMapRepl :: Int -> Int -> Int -> IO () concatMapRepl outer inner n = drain $ toStream $ StreamK.concatMap (fromStream . S.replicate inner) (fromStream $ sourceUnfoldrM outer n) #ifdef INSPECTION #if __GLASGOW_HASKELL__ >= 906 inspect $ hasNoTypeClassesExcept 'concatMapRepl [''Applicative] #else inspect $ hasNoTypeClasses 'concatMapRepl #endif inspect $ 'concatMapRepl `hasNoType` ''SPEC #endif -- concatMapWith #ifdef USE_STREAMK {-# INLINE concatMapWithSerial #-} concatMapWithSerial :: Int -> Int -> Int -> IO () concatMapWithSerial = concatStreamsWith Common.append #ifdef INSPECTION inspect $ hasNoTypeClasses 'concatMapWithSerial inspect $ 'concatMapWithSerial `hasNoType` ''SPEC #endif {- {-# INLINE concatMapWithAppend #-} concatMapWithAppend :: Int -> Int -> Int -> IO () concatMapWithAppend = concatStreamsWith Common.append2 #ifdef INSPECTION inspect $ hasNoTypeClasses 'concatMapWithAppend inspect $ 'concatMapWithAppend `hasNoType` ''SPEC #endif -} -- mergeMapWith {-# INLINE mergeMapWithSerial #-} mergeMapWithSerial :: Int -> Int -> Int -> IO () mergeMapWithSerial = mergeMapWith Common.append {- {-# INLINE mergeMapWithAppend #-} mergeMapWithAppend :: Int -> Int -> Int -> IO () mergeMapWithAppend = mergeMapWith Common.append2 -} #endif -- unfoldMany -- unfoldMany replicate/unfoldrM {-# INLINE unfoldManyRepl #-} unfoldManyRepl :: Int -> Int -> Int -> IO () unfoldManyRepl outer inner n = drain $ S.unfoldMany UF.replicateM (fmap ((inner,) . return) (sourceUnfoldrM outer n)) #ifdef INSPECTION inspect $ hasNoTypeClasses 'unfoldManyRepl inspect $ 'unfoldManyRepl `hasNoType` ''D.ConcatMapUState inspect $ 'unfoldManyRepl `hasNoType` ''SPEC #endif o_1_space_concat :: Int -> [Benchmark] o_1_space_concat value = sqrtVal `seq` [ bgroup "concat" [ benchIOSrc1 "concatMapPure (n of 1)" (concatMapPure value 1) , benchIOSrc1 "concatMapPure (sqrt n of sqrt n)" (concatMapPure sqrtVal sqrtVal) , benchIOSrc1 "concatMapPure (1 of n)" (concatMapPure 1 value) , benchIOSrc1 "concatMap (n of 1)" (concatMap value 1) , benchIOSrc1 "concatMap (sqrt n of sqrt n)" (concatMap sqrtVal sqrtVal) , benchIOSrc1 "concatMap (1 of n)" (concatMap 1 value) #ifndef USE_STREAMK -- This is for comparison with foldMapWith , benchIOSrc "concatMapId (n of 1) (fromFoldable)" (S.concatMap id . sourceConcatMapId value) , benchIOSrc1 "concatMapM (n of 1)" (concatMapM value 1) , benchIOSrc1 "concatMapM (sqrt n of sqrt n)" (concatMapM sqrtVal sqrtVal) , benchIOSrc1 "concatMapM (1 of n)" (concatMapM 1 value) #endif #ifdef USE_STREAMK {- -- This is for comparison with foldMapWith , benchIOSrc "concatMapWithId (n of 1) (fromFoldable)" (toStream . S.concatMapWith Common.append id . sourceConcatMapId value) -} , benchIOSrc1 "concatMapWith (n of 1)" (concatMapWithSerial value 1) , benchIOSrc1 "concatMapWith (sqrt n of sqrt n)" (concatMapWithSerial sqrtVal sqrtVal) , benchIOSrc1 "concatMapWith (1 of n)" (concatMapWithSerial 1 value) {- -- quadratic with number of outer streams , benchIOSrc1 "concatMapWithAppend (2 of n/2)" (concatMapWithAppend 2 (value `div` 2)) -} #endif -- concatMap vs unfoldMany , benchIOSrc1 "concatMapRepl (sqrt n of sqrt n)" (concatMapRepl sqrtVal sqrtVal) , benchIOSrc1 "unfoldManyRepl (sqrt n of sqrt n)" (unfoldManyRepl sqrtVal sqrtVal) ] ] where sqrtVal = round $ sqrt (fromIntegral value :: Double) #ifdef USE_STREAMK o_n_space_merge :: Int -> [Benchmark] o_n_space_merge value = sqrtVal `seq` [ bgroup "concat" [ -------------------mergeMapWith----------------- -- Use large number of streams to check scalability benchIOSrc1 "mergeMapWithSerial (n of 1)" (mergeMapWithSerial value 1) , benchIOSrc1 "mergeMapWithSerial (sqrtVal of sqrtVal)" (mergeMapWithSerial sqrtVal sqrtVal) , benchIOSrc1 "mergeMapWithSerial (2 of n/2)" (mergeMapWithSerial 2 (value `div` 2)) {- , benchIOSrc1 "mergeMapWithAppend (n of 1)" (mergeMapWithAppend value 1) , benchIOSrc1 "mergeMapWithAppend (sqrtVal of sqrtVal)" (mergeMapWithAppend sqrtVal sqrtVal) -} ] ] where sqrtVal = round $ sqrt (fromIntegral value :: Double) #endif ------------------------------------------------------------------------------- -- Applicative ------------------------------------------------------------------------------- o_1_space_applicative :: Int -> [Benchmark] o_1_space_applicative value = [ bgroup "Applicative" [ benchIO "(*>) (sqrt n x sqrt n)" $ apDiscardFst value , benchIO "(<*) (sqrt n x sqrt n)" $ apDiscardSnd value , benchIO "(<*>) (sqrt n x sqrt n)" $ toNullAp value , benchIO "liftA2 (sqrt n x sqrt n)" $ apLiftA2 value ] ] #ifdef USE_STREAMK o_n_space_applicative :: Int -> [Benchmark] o_n_space_applicative value = [ bgroup "iterated" [ benchIOSrc "(*>) (n times)" $ iterateSingleton ((*>) . pure) value , benchIOSrc "(<*) (n times)" $ iterateSingleton (\x xs -> xs <* pure x) value , benchIOSrc "(<*>) (n times)" $ iterateSingleton (\x xs -> pure (+ x) <*> xs) value , benchIOSrc "liftA2 (n times)" $ iterateSingleton (AP.liftA2 (+) . pure) value ] ] #endif ------------------------------------------------------------------------------- -- Monad ------------------------------------------------------------------------------- o_1_space_monad :: Int -> [Benchmark] o_1_space_monad value = [ bgroup "Monad" [ benchIO "(>>) (sqrt n x sqrt n)" $ monadThen value , benchIO "(>>=) (sqrt n x sqrt n)" $ toNullM value , benchIO "(>>=) (sqrt n x sqrt n) (filterAllOut)" $ filterAllOutM value , benchIO "(>>=) (sqrt n x sqrt n) (filterAllIn)" $ filterAllInM value , benchIO "(>>=) (sqrt n x sqrt n) (filterSome)" $ filterSome value , benchIO "(>>=) (sqrt n x sqrt n) (breakAfterSome)" $ breakAfterSome value , benchIO "(>>=) (cubert n x cubert n x cubert n)" $ toNullM3 value ] ] #ifdef USE_STREAMK -- This is a good benchmark but inefficient way to compute primes. As we see a -- new prime we keep appending a division filter for all the future numbers. {-# INLINE sieve #-} sieve :: Monad m => StreamK m Int -> StreamK m Int sieve s = StreamK.concatEffect $ do r <- StreamK.uncons s case r of Just (prime, rest) -> -- XXX Use K.filter or rewrite to K.filter let f = S.filter (\n -> n `mod` prime /= 0) in pure $ prime `StreamK.cons` sieve (fromStream $ f $ toStream rest) Nothing -> pure StreamK.nil o_n_space_iterated :: Int -> [Benchmark] o_n_space_iterated value = [ bgroup "iterated" [ benchIO "concatEffect prime sieve (n/4)" (\n -> S.fold Fold.sum $ toStream $ sieve $ fromStream $ S.enumerateFromTo 2 (value `div` 4 + n)) , benchIOSrc "(>>) (n times)" $ iterateSingleton ((>>) . pure) value , benchIOSrc "(>>=) (n times)" $ iterateSingleton (\x xs -> xs >>= \y -> return (x + y)) value ] ] #endif o_n_space_monad :: Int -> [Benchmark] o_n_space_monad value = [ bgroup "Monad" [ benchIO "(>>=) (sqrt n x sqrt n) (toList)" $ toListM value , benchIO "(>>=) (sqrt n x sqrt n) (toListSome)" $ toListSome value ] ] ------------------------------------------------------------------------------- -- Joining ------------------------------------------------------------------------------- {- toKv :: Int -> (Int, Int) toKv p = (p, p) {-# INLINE joinWith #-} joinWith :: Common.MonadAsync m => ((Int -> Int -> Bool) -> Stream m Int -> Stream m Int -> Stream m b) -> Int -> Int -> m () joinWith j val i = drain $ j (==) (sourceUnfoldrM val i) (sourceUnfoldrM val (val `div` 2)) {-# INLINE joinMapWith #-} joinMapWith :: Common.MonadAsync m => (Stream m (Int, Int) -> Stream m (Int, Int) -> Stream m b) -> Int -> Int -> m () joinMapWith j val i = drain $ j (fmap toKv (sourceUnfoldrM val i)) (fmap toKv (sourceUnfoldrM val (val `div` 2))) o_n_heap_buffering :: Int -> [Benchmark] o_n_heap_buffering value = [ bgroup "buffered" [ benchIOSrc1 "joinInnerGeneric (sqrtVal)" $ joinWith S.joinInnerGeneric sqrtVal , benchIOSrc1 "joinInner" $ joinMapWith S.joinInner halfVal , benchIOSrc1 "joinLeftGeneric (sqrtVal)" $ joinWith S.joinLeftGeneric sqrtVal , benchIOSrc1 "joinLeft " $ joinMapWith S.joinLeft halfVal , benchIOSrc1 "joinOuterGeneric (sqrtVal)" $ joinWith S.joinOuterGeneric sqrtVal , benchIOSrc1 "joinOuter" $ joinMapWith S.joinOuter halfVal , benchIOSrc1 "filterInStreamGenericBy (sqrtVal)" $ joinWith S.filterInStreamGenericBy sqrtVal , benchIOSrc1 "filterInStreamAscBy" $ joinMapWith (S.filterInStreamAscBy compare) halfVal ] ] where halfVal = value `div` 2 sqrtVal = round $ sqrt (fromIntegral value :: Double) -} ------------------------------------------------------------------------------- -- Main ------------------------------------------------------------------------------- -- In addition to gauge options, the number of elements in the stream can be -- passed using the --stream-size option. -- {-# ANN benchmarks "HLint: ignore" #-} benchmarks :: String -> Int -> [Benchmark] benchmarks moduleName size = [ bgroup (o_1_space_prefix moduleName) $ Prelude.concat [ -- multi-stream o_1_space_joining size #ifdef USE_PRELUDE , o_1_space_concatFoldable size #endif , o_1_space_concat size , o_1_space_applicative size , o_1_space_monad size ] , bgroup (o_n_space_prefix moduleName) $ Prelude.concat [ -- multi-stream o_n_space_monad size #ifdef USE_STREAMK , o_n_space_merge size , o_n_space_iterated size , o_n_space_applicative size #endif ] {- , bgroup (o_n_heap_prefix moduleName) $ -- multi-stream o_n_heap_buffering size -} ]