streaming-benchmarks 0.1.0 → 0.2.0
raw patch · 20 files changed
+2059/−814 lines, 20 filesdep +bench-graphdep +drinkerydep +getopt-genericsdep −list-tdep −list-transformerdep −logictdep ~Chartdep ~Chart-diagramsdep ~gauge
Dependencies added: bench-graph, drinkery, getopt-generics, template-haskell
Dependencies removed: list-t, list-transformer, logict
Dependency ranges changed: Chart, Chart-diagrams, gauge, streamly
Files
- Benchmarks.hs +79/−435
- Benchmarks/BenchmarkTH.hs +44/−0
- Benchmarks/Common.hs +39/−0
- Benchmarks/Conduit.hs +151/−0
- Benchmarks/Drinkery.hs +125/−0
- Benchmarks/List.hs +114/−0
- Benchmarks/Machines.hs +129/−0
- Benchmarks/Pipes.hs +140/−0
- Benchmarks/Streaming.hs +147/−0
- Benchmarks/Streamly.hs +167/−0
- Benchmarks/Vector.hs +165/−0
- Benchmarks/VectorPure.hs +116/−0
- Changelog.md +24/−0
- Charts.hs +108/−160
- README.rst +381/−153
- run.sh +39/−12
- stack-8.2.yaml +21/−0
- stack-pedantic.yaml +8/−14
- stack.yaml +9/−14
- streaming-benchmarks.cabal +53/−26
Benchmarks.hs view
@@ -1,449 +1,93 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE RankNTypes #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}--module Main (main) where--import Control.DeepSeq (NFData)-import Control.Monad.IO.Class (MonadIO(liftIO))-import Control.Monad.Trans.Class (lift)-import Gauge-import Data.Foldable (msum)-import Data.Function ((&))---import System.Random (randomIO)-import System.Random (randomRIO)--import qualified Streamly as A-import qualified Streamly.Prelude as A-import qualified Data.Conduit as C-import qualified Data.Conduit.Combinators as CC-import qualified Data.Conduit.List as C-import qualified List.Transformer as L-import qualified ListT as LT-import qualified Control.Monad.Logic as LG-import qualified Data.Machine as M-import qualified Pipes as P-import qualified Pipes.Prelude as P-import qualified Streaming.Prelude as S-import qualified Data.Vector.Fusion.Stream.Monadic as V--- import qualified Conduit.Simple as SC+-- |+-- Module : Main+-- Copyright : (c) 2018 Harendra Kumar ------ Orphan instance to use nfIO on streaming-instance (NFData a, NFData b) => NFData (S.Of a b)--getRandom :: MonadIO m => m Int-getRandom = liftIO $ randomRIO (1,1000)--value, maxValue :: Int-value = 1000000-maxValue = value + 1000------------------------------------------------------------------------------------ Streamly----------------------------------------------------------------------------------sourceA :: MonadIO m => A.StreamT m Int-sourceA = getRandom >>= \v -> A.each [v..v+value]---- Note, streamly provides two different ways to compose, i.e. category style--- composition and monadic compostion.---- Category composition-runIOA :: A.StreamT IO Int -> (A.StreamT IO Int -> A.StreamT IO Int) -> IO ()-runIOA s t = A.runStreamT $ s & t--{---- Monadic composition-runIOA_M :: A.StreamT IO Int -> (Int -> A.StreamT IO Int) -> IO ()-runIOA_M s t = A.runStreamT $ s >>= t--}------------------------------------------------------------------------------------ streaming----------------------------------------------------------------------------------sourceS :: MonadIO m => S.Stream (S.Of Int) m ()-sourceS = getRandom >>= \v -> S.each [v..v+value]--runIOS :: S.Stream (S.Of Int) IO ()- -> (S.Stream (S.Of Int) IO () -> S.Stream (S.Of Int) IO ()) -> IO ()-runIOS s t = s & t & S.mapM_ (\_ -> return ())------------------------------------------------------------------------------------ simple-conduit----------------------------------------------------------------------------------{--sourceSC :: MonadIO m => SC.Source m Int-sourceSC = getRandom >>= \v -> SC.enumFromToC v (v + value)--runIOSC :: SC.Source IO Int -> SC.Conduit Int IO a -> IO ()-runIOSC s t = s SC.$= t SC.$$ SC.mapM_C (\_ -> return ())--}------------------------------------------------------------------------------------ conduit----------------------------------------------------------------------------------sourceC :: MonadIO m => C.ConduitT () Int m ()-sourceC = getRandom >>= \v -> C.enumFromTo v (v + value)--runIOC :: C.ConduitT () Int IO () -> C.ConduitT Int a IO () -> IO ()-runIOC s t = C.runConduit $ s C..| t C..| C.mapM_ (\_ -> return ())------------------------------------------------------------------------------------ pipes----------------------------------------------------------------------------------sourceP :: MonadIO m => P.Producer' Int m ()-sourceP = getRandom >>= \v -> P.each [v..v+value]--runIOP :: P.Producer' Int IO () -> P.Proxy () Int () a IO () -> IO ()-runIOP s t = P.runEffect $ s P.>-> t P.>-> P.mapM_ (\_ -> return ())------------------------------------------------------------------------------------ machines----------------------------------------------------------------------------------sourceM :: Monad m => Int -> M.SourceT m Int-sourceM v = M.enumerateFromTo v (v + value)--runIOM :: M.SourceT IO Int -> M.ProcessT IO Int o -> IO ()-runIOM s t = M.runT_ (s M.~> t)------------------------------------------------------------------------------------ list-transformer----------------------------------------------------------------------------------sourceL :: MonadIO m => L.ListT m Int-sourceL = getRandom >>= \v -> L.select [v..v+value]--runIOL :: L.ListT IO Int -> (Int -> L.ListT IO Int) -> IO ()-runIOL s t = L.runListT (s >>= t)------------------------------------------------------------------------------------ list-t----------------------------------------------------------------------------------sourceLT :: MonadIO m => LT.ListT m Int-sourceLT = getRandom >>= \v -> LT.fromFoldable [v..v+value]--runIOLT :: LT.ListT IO Int -> (Int -> LT.ListT IO Int) -> IO ()-runIOLT s t = LT.traverse_ (\_ -> return ()) (s >>= t)------------------------------------------------------------------------------------ logict----------------------------------------------------------------------------------sourceLG :: Int -> LG.LogicT m Int-sourceLG v = msum $ map return [v..v+value]+-- License : MIT+-- Maintainer : harendra.kumar@gmail.com -runIOLG :: LG.LogicT IO Int -> (Int -> LG.LogicT IO Int) -> IO ()-runIOLG s t = LG.observeAllT (s >>= t) >> return ()+{-# LANGUAGE TemplateHaskell #-} ----------------------------------------------------------------------------------- vector--------------------------------------------------------------------------------+module Main (main) where -sourceV :: Monad m => Int -> V.Stream m Int-sourceV v = V.fromList [v..v+value]+import Benchmarks.BenchmarkTH (createBgroup)+import Benchmarks.Common (benchIO)+--import Benchmarks.BenchmarkTH (createScaling) -runIOV :: V.Stream IO Int -> (V.Stream IO Int -> V.Stream IO Int) -> IO ()-runIOV s t = s & t & V.mapM_ (\_ -> return ())+import qualified Benchmarks.Vector as Vector+import qualified Benchmarks.Streamly as Streamly+import qualified Benchmarks.Streaming as Streaming+import qualified Benchmarks.Machines as Machines+import qualified Benchmarks.Pipes as Pipes+import qualified Benchmarks.Conduit as Conduit+import qualified Benchmarks.Drinkery as Drinkery+import qualified Benchmarks.List as List+import qualified Benchmarks.VectorPure as VectorPure+-- import qualified Benchmarks.LogicT as LogicT+-- import qualified Benchmarks.ListT as ListT+-- import qualified Benchmarks.ListTransformer as ListTransformer ----------------------------------------------------------------------------------- Benchmarks--------------------------------------------------------------------------------+import Gauge main :: IO ()-main =+main = do defaultMain- [ bgroup "elimination"- [- bgroup "toNull"- [- bench "conduit" $ nfIO $ C.runConduit $ sourceC C..| C.mapM_ (\_ -> return ())- , bench "pipes" $ nfIO $ P.runEffect $ sourceP P.>-> P.mapM_ (\_ -> return ())- , bench "machines" $ nfIO $ getRandom >>= \v -> M.runT_ (sourceM v)- , bench "streaming" $ nfIO $ runIOS sourceS id- , bench "streamly" $ nfIO $ runIOA sourceA id- -- , bench "simple-conduit" $ nfIO $ sourceSC SC.$$ SC.mapM_C (\_ -> return ())- , bench "logict" $ nfIO $ getRandom >>= \v -> LG.observeAllT (sourceLG v) >> return ()- , bench "list-t" $ nfIO $ LT.traverse_ (\_ -> return ()) sourceLT- , bench "list-transformer" $ nfIO $ L.runListT sourceL- , bench "vector" $ nfIO $ getRandom >>= \v -> runIOV (sourceV v) id- ]- , bgroup "toList"- [- bench "conduit" $ nfIO $ C.runConduit $ sourceC C..| CC.sinkList- , bench "pipes" $ nfIO $ P.toListM sourceP- , bench "machines" $ nfIO $ getRandom >>= \v -> M.runT (sourceM v)- , bench "streaming" $ nfIO $ S.toList sourceS- , bench "streamly" $ nfIO $ A.toList sourceA- -- , bench "simple-conduit" $ nfIO $ sourceSC SC.$$ SC.sinkList- , bench "logict" $ nfIO $ getRandom >>= \v -> LG.observeAllT (sourceLG v) >> return ()- , bench "list-t" $ nfIO $ LT.toList sourceLT- -- , bench "list-transformer" $ nfIO $ toList sourceL- , bench "vector" $ nfIO $ getRandom >>= \v -> V.toList (sourceV v)- ]- , bgroup "fold"- [ bench "streamly" $ nfIO $ A.foldl (+) 0 id sourceA- , bench "streaming" $ nfIO $ S.fold (+) 0 id sourceS- , bench "conduit" $ nfIO $ C.runConduit $ sourceC C..| (C.fold (+) 0)- , bench "pipes" $ nfIO $ P.fold (+) 0 id sourceP- , bench "machines" $ nfIO $ getRandom >>= \v -> runIOM (sourceM v) (M.fold (+) 0)- , bench "list-transformer" $ nfIO $ L.fold (+) 0 id sourceL- , bench "vector" $ nfIO $ getRandom >>= \v -> V.foldl' (+) 0 (sourceV v)- ]- , bgroup "scan"- [ bench "conduit" $ nfIO $ runIOC sourceC (CC.scanl (+) 0)- , bench "pipes" $ nfIO $ runIOP sourceP (P.scan (+) 0 id)- , bench "machines" $ nfIO $ getRandom >>= \v -> runIOM (sourceM v) (M.scan (+) 0)- , bench "streaming" $ nfIO $ runIOS sourceS (S.scan (+) 0 id)- , bench "streamly" $ nfIO $ runIOA sourceA (A.scan (+) 0 id)- , bench "vector" $ nfIO $ getRandom >>= \v -> runIOV (sourceV v) (V.prescanl' (+) 0)- ]- , bgroup "last"- [ bench "pipes" $ nfIO $ P.last sourceP- , bench "machines" $ nfIO $ getRandom >>= \v -> runIOM (sourceM v) (M.final)- , bench "streaming" $ nfIO $ S.last sourceS- , bench "streamly" $ nfIO $ A.last sourceA- , bench "conduit" $ nfIO $ C.runConduit $ sourceC C..| CC.last- , bench "vector" $ nfIO $ getRandom >>= \v -> V.last (sourceV v)- ]- , bgroup "concat"- [ bench "conduit" $ nfIO $ runIOC sourceC (C.map (replicate 3) C..| C.concat)- , bench "pipes" $ nfIO $ runIOP sourceP (P.map (replicate 3) P.>-> P.concat)- , bench "machines" $ nfIO $ getRandom >>= \v -> runIOM (sourceM v) (M.mapping (replicate 3) M.~> M.asParts)- -- XXX This hangs indefinitely- -- , bench "streaming" $ nfIO $ runIOS sourceS (S.concat . S.map (replicate 3))- , bench "vector" $ nfIO $ getRandom >>= \v -> runIOV (sourceV v) (V.concatMap (V.fromList . replicate 3))- ]- ]+ [ bgroup "elimination"+ [ $(createBgroup "drain" "toNull")+ , $(createBgroup "toList" "toList")+ , $(createBgroup "fold" "foldl")+ , $(createBgroup "last" "last")+ ] , bgroup "transformation"- [ bgroup "map"- [ bench "conduit" $ nfIO $ runIOC sourceC (C.map (+1))- , bench "pipes" $ nfIO $ runIOP sourceP (P.map (+1))- , bench "machines" $ nfIO $ getRandom >>= \v -> runIOM (sourceM v) (M.mapping (+1))- , bench "streaming" $ nfIO $ runIOS sourceS (S.map (+1))- , bench "streamly" $ nfIO $ runIOA sourceA (fmap (+1))- -- , bench "simple-conduit" $ nfIO $ runIOSC sourceSC (SC.mapC (+1))- , bench "list-transformer" $ nfIO $ runIOL sourceL (lift . return . (+1))- , bench "vector" $ nfIO $ getRandom >>= \v -> runIOV (sourceV v) (V.map (+1))- ]- , bgroup "mapM"- [ bench "conduit" $ nfIO $ runIOC sourceC (C.mapM return)- , bench "pipes" $ nfIO $ runIOP sourceP (P.mapM return)- , bench "machines" $ nfIO $ getRandom >>= \v -> runIOM (sourceM v) (M.autoM return)- , bench "streaming" $ nfIO $ runIOS sourceS (S.mapM return)- , bench "streamly" $ nfIO $ runIOA sourceA (A.mapM return)- , bench "vector" $ nfIO $ getRandom >>= \v -> runIOV (sourceV v) (V.mapM return)- ]- ]+ [ $(createBgroup "scan" "scan")+ , $(createBgroup "map" "map")+ , $(createBgroup "mapM" "mapM")+ , $(createBgroup "concat" "concat")+ ] , bgroup "filtering"- [ bgroup "filter-even"- [ bench "conduit" $ nfIO $ runIOC sourceC (C.filter even)- , bench "pipes" $ nfIO $ runIOP sourceP (P.filter even)- , bench "machines" $ nfIO $ getRandom >>= \v -> runIOM (sourceM v) (M.filtered even)- , bench "streaming" $ nfIO $ runIOS sourceS (S.filter even)- , bench "streamly" $ nfIO $ runIOA sourceA (A.filter even)- , bench "vector" $ nfIO $ getRandom >>= \v -> runIOV (sourceV v) (V.filter even)- ]- , bgroup "filter-all-out"- [ bench "conduit" $ nfIO $ runIOC sourceC (C.filter (> maxValue))- , bench "pipes" $ nfIO $ runIOP sourceP (P.filter (> maxValue))- , bench "machines" $ nfIO $ getRandom >>= \v -> runIOM (sourceM v) (M.filtered (> maxValue))- , bench "streaming" $ nfIO $ runIOS sourceS (S.filter (> maxValue))- , bench "streamly" $ nfIO $ runIOA sourceA (A.filter (> maxValue))- , bench "vector" $ nfIO $ getRandom >>= \v -> runIOV (sourceV v) (V.filter (> maxValue))- ]- , bgroup "filter-all-in"- [ bench "conduit" $ nfIO $ runIOC sourceC (C.filter (<= maxValue))- , bench "pipes" $ nfIO $ runIOP sourceP (P.filter (<= maxValue))- , bench "machines" $ nfIO $ getRandom >>= \v -> runIOM (sourceM v) (M.filtered (<= maxValue))- , bench "streaming" $ nfIO $ runIOS sourceS (S.filter (<= maxValue))- , bench "streamly" $ nfIO $ runIOA sourceA (A.filter (<= maxValue))- , bench "vector" $ nfIO $ getRandom >>= \v -> runIOV (sourceV v) (V.filter (<= maxValue))- ]- , bgroup "take-one"- [ bench "conduit" $ nfIO $ runIOC sourceC (C.isolate 1)- , bench "pipes" $ nfIO $ runIOP sourceP (P.take 1)- , bench "machines" $ nfIO $ getRandom >>= \v -> runIOM (sourceM v) (M.taking 1)- , bench "streaming" $ nfIO $ runIOS sourceS (S.take 1)- , bench "streamly" $ nfIO $ runIOA sourceA (A.take 1)- , bench "vector" $ nfIO $ getRandom >>= \v -> runIOV (sourceV v) (V.take 1)- ]- -- XXX variance need to be fixed, value used is not correct- , bgroup "take-all"- [ bench "conduit" $ nfIO $ runIOC sourceC (C.isolate maxValue)- , bench "pipes" $ nfIO $ runIOP sourceP (P.take maxValue)- , bench "machines" $ nfIO $ getRandom >>= \v -> runIOM (sourceM v) (M.taking maxValue)- , bench "streaming" $ nfIO $ runIOS sourceS (S.take maxValue)- , bench "streamly" $ nfIO $ runIOA sourceA (A.take maxValue)- -- , bench "list-transformer" $ nfIO $ (runIdentity . L.runListT) (L.take value sourceL :: L.ListT Identity Int)- , bench "vector" $ nfIO $ getRandom >>= \v -> runIOV (sourceV v) (V.take maxValue)- ]- , bgroup "takeWhile-true"- [ bench "conduit" $ nfIO $ runIOC sourceC (CC.takeWhile (<= maxValue))- , bench "pipes" $ nfIO $ runIOP sourceP (P.takeWhile (<= maxValue))- , bench "machines" $ nfIO $ getRandom >>= \v -> runIOM (sourceM v) (M.takingWhile (<= maxValue))- , bench "streaming" $ nfIO $ runIOS sourceS (S.takeWhile (<= maxValue))- , bench "streamly" $ nfIO $ runIOA sourceA (A.takeWhile (<= maxValue))- , bench "vector" $ nfIO $ getRandom >>= \v -> runIOV (sourceV v) (V.takeWhile (<= maxValue))- ]- , bgroup "drop-all"- [ bench "conduit" $ nfIO $ runIOC sourceC (C.drop maxValue)- , bench "pipes" $ nfIO $ runIOP sourceP (P.drop maxValue)- , bench "machines" $ nfIO $ getRandom >>= \v -> runIOM (sourceM v) (M.dropping maxValue)- , bench "streaming" $ nfIO $ runIOS sourceS (S.drop maxValue)- , bench "streamly" $ nfIO $ runIOA sourceA (A.drop maxValue)- -- , bench "simple-conduit" $ whnf drainSC (SC.dropC value)- --, bench "list-transformer" $ whnf (runIdentity . L.runListT) (L.drop value sourceL :: L.ListT Identity Int)- , bench "vector" $ nfIO $ getRandom >>= \v -> runIOV (sourceV v) (V.drop maxValue)- ]- , bgroup "dropWhile-true"- [ bench "conduit" $ nfIO $ runIOC sourceC (CC.dropWhile (<= maxValue))- , bench "pipes" $ nfIO $ runIOP sourceP (P.dropWhile (<= maxValue))- , bench "machines" $ nfIO $ getRandom >>= \v -> runIOM (sourceM v) (M.droppingWhile (<= maxValue))- , bench "streaming" $ nfIO $ runIOS sourceS (S.dropWhile (<= maxValue))- , bench "streamly" $ nfIO $ runIOA sourceA (A.dropWhile (<= maxValue))- , bench "vector" $ nfIO $ getRandom >>= \v -> runIOV (sourceV v) (V.dropWhile (<= maxValue))- ]- ]- , bgroup "zip"- [ bench "conduit" $ nfIO $ C.runConduit $ (C.getZipSource $ (,) <$> C.ZipSource sourceC <*> C.ZipSource sourceC) C..| C.sinkNull- , bench "pipes" $ nfIO $ P.runEffect $ P.for (P.zip sourceP sourceP) P.discard- , bench "machines" $ nfIO $ getRandom >>= \v1 -> getRandom >>= \v2 -> M.runT_ (M.capT (sourceM v1) (sourceM v2) M.zipping)- , bench "streaming" $ nfIO $ S.effects (S.zip sourceS sourceS)- , bench "streamly" $ nfIO $ A.runStreamT $ (A.zipWith (,) sourceA sourceA)- , bench "vector" $ nfIO $ getRandom >>= \v1 -> getRandom >>= \v2 -> V.mapM_ return $ (V.zipWith (,) (sourceV v1) (sourceV v2))- ]- -- Composing multiple stages of a pipeline+ [ $(createBgroup "filter-even" "filterEven")+ , $(createBgroup "filter-all-out" "filterAllOut")+ , $(createBgroup "filter-all-in" "filterAllIn")+ , $(createBgroup "take-all" "takeAll")+ , $(createBgroup "takeWhile-true" "takeWhileTrue")+ , $(createBgroup "drop-all" "dropAll")+ , $(createBgroup "dropWhile-true" "dropWhileTrue")+ ]+ , $(createBgroup "zip" "zip")+ , bgroup "append"+ [ benchIO "streamly" Streamly.appendSource Streamly.toNull+ , benchIO "conduit" Conduit.appendSource Conduit.toNull+-- , benchIO "pipes" Pipes.appendSource Pipes.toNull+ , bench "pipes" $ nfIO (return 1 :: IO Int)+-- , benchIO "vector" Vector.appendSource Vector.toNull+ , bench "vector" $ nfIO (return 1 :: IO Int)+-- , benchIO "streaming" Streaming.appendSource Streaming.toNull+ , bench "streaming" $ nfIO (return 1 :: IO Int)+ ]+ {-+ -- Perform 100,000 mapM recursively over a stream of length 10+ -- implemented only for vector and streamly.+ bgroup "mapM-nested"+ , [ benchIO "streamly" Streamly.mapMSource Streamly.toNull+ , benchIO "vector" Vector.mapMSource Vector.toNull+ ]+ -} , bgroup "compose"- [- {-- let f x =- if (x `mod` 4 == 0)- then- randomIO- else return x- -}- let f = return- c = C.mapM f- p = P.mapM f- m = M.autoM f- s = S.mapM f- a = A.mapM f- u = V.mapM f- lb = lift . f- l = lift . f- lg = lift . f- in bgroup "mapM"- [ bench "conduit" $ nfIO $ runIOC sourceC $ c C..| c C..| c C..| c- , bench "pipes" $ nfIO $ runIOP sourceP $ p P.>-> p P.>-> p P.>-> p- , bench "machines" $ nfIO $ getRandom >>= \v -> runIOM (sourceM v) $ m M.~> m M.~> m M.~> m- , bench "streaming" $ nfIO $ runIOS sourceS $ \x -> s x & s & s & s- , bench "streamly" $ nfIO $ runIOA sourceA $ \x -> a x & a & a & a- , bench "list-t" $ nfIO $ runIOLT sourceLT $ \x -> lb x >>= lb >>= lb >>= lb- , bench "list-transformer" $ nfIO $ runIOL sourceL $ \x -> l x >>= l >>= l >>= l- , bench "logict" $ nfIO $ getRandom >>= \v -> runIOLG (sourceLG v) $ \x -> lg x >>= lg >>= lg >>= lg- , bench "vector" $ nfIO $ getRandom >>= \v -> runIOV (sourceV v) $ \x -> u x & u & u & u- ]-- -- XXX should we use a monadic mapM instead?- , let m = M.mapping (subtract 1) M.~> M.filtered (<= maxValue)- s = S.filter (<= maxValue) . S.map (subtract 1)- a = A.filter (<= maxValue) . fmap (subtract 1)- p = P.map (subtract 1) P.>-> P.filter (<= maxValue)- c = C.map (subtract 1) C..| C.filter (<= maxValue)- u = V.filter (<= maxValue) . V.map (subtract 1)- in bgroup "map-with-all-in-filter"- [ bench "conduit" $ nfIO $ runIOC sourceC $ c C..| c C..| c C..| c- , bench "pipes" $ nfIO $ runIOP sourceP $ p P.>-> p P.>-> p P.>-> p- , bench "machines" $ nfIO $ getRandom >>= \v -> runIOM (sourceM v) $ m M.~> m M.~> m M.~> m- , bench "streaming" $ nfIO $ runIOS sourceS $ \x -> s x & s & s & s- , bench "streamly" $ nfIO $ runIOA sourceA $ \x -> a x & a & a & a- , bench "vector" $ nfIO $ getRandom >>= \v -> runIOV (sourceV v) $ \x -> u x & u & u & u- ]-- -- Compose multiple ops, all stages letting everything through.- -- Note, IO monad makes a big difference especially for machines.- , let m = M.filtered (<= maxValue)- a = A.filter (<= maxValue)- s = S.filter (<= maxValue)- p = P.filter (<= maxValue)- c = C.filter (<= maxValue)- u = V.filter (<= maxValue)- in bgroup "all-in-filters"- [ bench "conduit" $ nfIO $ runIOC sourceC $ c C..| c C..| c C..| c- , bench "pipes" $ nfIO $ runIOP sourceP $ p P.>-> p P.>-> p P.>-> p- , bench "machines" $ nfIO $ getRandom >>= \v -> runIOM (sourceM v) $ m M.~> m M.~> m M.~> m- , bench "streaming" $ nfIO $ runIOS sourceS $ \x -> s x & s & s & s- , bench "streamly" $ nfIO $ runIOA sourceA $ \x -> a x & a & a & a- , bench "vector" $ nfIO $ getRandom >>= \v -> runIOV (sourceV v) $ \x -> u x & u & u & u- ]-- -- how filtering affects the subsequent composition- , let m = M.filtered (> maxValue)- a = A.filter (> maxValue)- s = S.filter (> maxValue)- p = P.filter (> maxValue)- c = C.filter (> maxValue)- u = V.filter (> maxValue)- in bgroup "all-out-filters"- [ bench "conduit" $ nfIO $ runIOC sourceC $ c C..| c C..| c C..| c- , bench "pipes" $ nfIO $ runIOP sourceP $ p P.>-> p P.>-> p P.>-> p- , bench "machines" $ nfIO $ getRandom >>= \v -> runIOM (sourceM v) $ m M.~> m M.~> m M.~> m- , bench "streaming" $ nfIO $ runIOS sourceS $ \x -> s x & s & s & s- , bench "streamly" $ nfIO $ runIOA sourceA $ \x -> a x & a & a & a- , bench "vector" $ nfIO $ getRandom >>= \v -> runIOV (sourceV v) $ \x -> u x & u & u & u- ]- ]+ [ $(createBgroup "mapM" "composeMapM")+ , $(createBgroup "map-with-all-in-filter" "composeMapAllInFilter")+ , $(createBgroup "all-in-filters" "composeAllInFilters")+ , $(createBgroup "all-out-filters" "composeAllOutFilters")+ ]+ -- XXX Disabling this for now to reduce the running time+ -- We need a way to include/exclude this dynamically+ {- , bgroup "compose-scaling"- [ -- Scaling with same operation in sequence- let f = M.filtered (<= maxValue)- in bgroup "machines-filters"- [ bench "1" $ nfIO $ getRandom >>= \v -> runIOM (sourceM v) f- , bench "2" $ nfIO $ getRandom >>= \v -> runIOM (sourceM v) $ f M.~> f- , bench "3" $ nfIO $ getRandom >>= \v -> runIOM (sourceM v) $ f M.~> f M.~> f- , bench "4" $ nfIO $ getRandom >>= \v -> runIOM (sourceM v) $ f M.~> f M.~> f M.~> f- ]- , let f = A.filter (<= maxValue)- in bgroup "streamly-filters"- [ bench "1" $ nfIO $ runIOA sourceA (\x -> f x)- , bench "2" $ nfIO $ runIOA sourceA $ \x -> f x & f- , bench "3" $ nfIO $ runIOA sourceA $ \x -> f x & f & f- , bench "4" $ nfIO $ runIOA sourceA $ \x -> f x & f & f & f- ]- , let f = S.filter (<= maxValue)- in bgroup "streaming-filters"- [ bench "1" $ nfIO $ runIOS sourceS (\x -> f x)- , bench "2" $ nfIO $ runIOS sourceS $ \x -> f x & f- , bench "3" $ nfIO $ runIOS sourceS $ \x -> f x & f & f- , bench "4" $ nfIO $ runIOS sourceS $ \x -> f x & f & f & f- ]- , let f = P.filter (<= maxValue)- in bgroup "pipes-filters"- [ bench "1" $ nfIO $ runIOP sourceP f- , bench "2" $ nfIO $ runIOP sourceP $ f P.>-> f- , bench "3" $ nfIO $ runIOP sourceP $ f P.>-> f P.>-> f- , bench "4" $ nfIO $ runIOP sourceP $ f P.>-> f P.>-> f P.>-> f- ]- , let f = C.filter (<= maxValue)- in bgroup "conduit-filters"- [ bench "1" $ nfIO $ runIOC sourceC f- , bench "2" $ nfIO $ runIOC sourceC $ f C..| f- , bench "3" $ nfIO $ runIOC sourceC $ f C..| f C..| f- , bench "4" $ nfIO $ runIOC sourceC $ f C..| f C..| f C..| f- ]- , let f = V.filter (<= maxValue)- in bgroup "vector-filters"- [ bench "1" $ nfIO $ getRandom >>= \v -> runIOV (sourceV v) (\x -> f x)- , bench "2" $ nfIO $ getRandom >>= \v -> runIOV (sourceV v) $ \x -> f x & f- , bench "3" $ nfIO $ getRandom >>= \v -> runIOV (sourceV v) $ \x -> f x & f & f- , bench "4" $ nfIO $ getRandom >>= \v -> runIOV (sourceV v) $ \x -> f x & f & f & f- ]- ]- ]+ [ $(createScaling "vector-filters" "Vector")+ , $(createScaling "streamly-filters" "Streamly")+ , $(createScaling "streaming-filters" "Streaming")+ , $(createScaling "machines-filters" "Machines")+ , $(createScaling "pipes-filters" "Pipes")+ , $(createScaling "conduit-filters" "Conduit")+ ]+ -}+ ]
+ Benchmarks/BenchmarkTH.hs view
@@ -0,0 +1,44 @@+{-# LANGUAGE TemplateHaskell #-}++module Benchmarks.BenchmarkTH (createBgroup, createScaling) where++import Benchmarks.Common (benchIO, benchPure)+--import Benchmarks.Common (benchId)+import Language.Haskell.TH.Syntax (Q, Exp, mkName)+import Language.Haskell.TH.Lib (varE)++createBgroup :: String -> String -> Q Exp+createBgroup name fname =+ [|+ bgroup name+ [ benchIO "vector" $(varE (mkName ("Vector.source")))+ $(varE (mkName ("Vector." ++ fname)))+ , benchIO "streamly" $(varE (mkName ("Streamly.source")))+ $(varE (mkName ("Streamly." ++ fname)))+ , benchIO "streaming" $(varE (mkName ("Streaming.source")))+ $(varE (mkName ("Streaming." ++ fname)))+ , benchIO "machines" $(varE (mkName ("Machines.source")))+ $(varE (mkName ("Machines." ++ fname)))+ , benchIO "pipes" $(varE (mkName ("Pipes.source")))+ $(varE (mkName ("Pipes." ++ fname)))+ , benchIO "conduit" $(varE (mkName ("Conduit.source")))+ $(varE (mkName ("Conduit." ++ fname)))+ , benchIO "drinkery" $(varE (mkName ("Drinkery.source")))+ $(varE (mkName ("Drinkery." ++ fname)))+ , benchPure "list" $(varE (mkName ("List.source")))+ $(varE (mkName ("List." ++ fname)))+ , benchPure "pure-vector" $(varE (mkName ("VectorPure.source")))+ $(varE (mkName ("VectorPure." ++ fname)))+ ]+ |]++createScaling :: String -> String -> Q Exp+createScaling name mname =+ [| let src = $(varE (mkName (mname ++ ".source")))+ in bgroup name+ [ benchIO "1" src ($(varE (mkName (mname ++ ".composeScaling"))) 1)+ , benchIO "2" src ($(varE (mkName (mname ++ ".composeScaling"))) 2)+ , benchIO "3" src ($(varE (mkName (mname ++ ".composeScaling"))) 3)+ , benchIO "4" src ($(varE (mkName (mname ++ ".composeScaling"))) 4)+ ]+ |]
+ Benchmarks/Common.hs view
@@ -0,0 +1,39 @@+-- |+-- Module : Benchmarks.Common+-- Copyright : (c) 2018 Harendra Kumar+--+-- License : MIT+-- Maintainer : harendra.kumar@gmail.com++module Benchmarks.Common+ ( value+ , maxValue+ , benchIO+ , benchId+ , benchPure+ ) where++import Control.DeepSeq (NFData)+import Data.Functor.Identity (Identity, runIdentity)+import System.Random (randomRIO)++import Gauge++value, maxValue :: Int+value = 1000000+maxValue = value + 1000++-- 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. This happens+-- specially in case of conduit, perhaps because of fusion?+{-# INLINE benchIO #-}+benchIO :: (NFData b) => String -> (Int -> a) -> (a -> IO b) -> Benchmark+benchIO name src f = bench name $ nfIO $ randomRIO (1,1000) >>= f . src++{-# INLINE benchId #-}+benchId :: (NFData b) => String -> (Int -> a) -> (a -> Identity b) -> Benchmark+benchId name src f = bench name $ nf (runIdentity . f) (src 10)++{-# INLINE benchPure #-}+benchPure :: NFData b => String -> (Int -> a) -> (a -> b) -> Benchmark+benchPure name src f = bench name $ nf f (src 10)
+ Benchmarks/Conduit.hs view
@@ -0,0 +1,151 @@+-- |+-- Module : Benchmarks.Conduit+-- Copyright : (c) 2018 Harendra Kumar+--+-- License : MIT+-- Maintainer : harendra.kumar@gmail.com++module Benchmarks.Conduit where++import Benchmarks.Common (value, maxValue)+import Prelude+ (Monad, Int, (+), ($), return, even, (>), (<=),+ subtract, undefined, replicate, (<$>), (<*>), Maybe(..), foldMap, (.))++import qualified Data.Conduit as S+import qualified Data.Conduit.Combinators as S+import qualified Data.Conduit.List as SL+-- import Data.Conduit.List (sourceList)++-------------------------------------------------------------------------------+-- Benchmark ops+-------------------------------------------------------------------------------++{-# INLINE toNull #-}+{-# INLINE toList #-}+{-# INLINE foldl #-}+{-# INLINE last #-}+{-# INLINE scan #-}+{-# INLINE map #-}+{-# INLINE filterEven #-}+{-# INLINE mapM #-}+{-# INLINE filterAllOut #-}+{-# INLINE filterAllIn #-}+{-# INLINE takeOne #-}+{-# INLINE takeAll #-}+{-# INLINE takeWhileTrue #-}+{-# INLINE dropAll #-}+{-# INLINE dropWhileTrue #-}+{-# INLINE zip #-}+{-# INLINE concat #-}+{-# INLINE composeMapM #-}+{-# INLINE composeAllInFilters #-}+{-# INLINE composeAllOutFilters #-}+{-# INLINE composeMapAllInFilter #-}+toNull, scan, map, filterEven, mapM, filterAllOut,+ filterAllIn, takeOne, takeAll, takeWhileTrue, dropAll, dropWhileTrue, zip,+ concat, composeMapM, composeAllInFilters, composeAllOutFilters,+ composeMapAllInFilter+ :: Monad m+ => Source m () Int -> m ()++toList :: Monad m => Source m () Int -> m [Int]+foldl :: Monad m => Source m () Int -> m Int+last :: Monad m => Source m () Int -> m (Maybe Int)++-------------------------------------------------------------------------------+-- Stream generation and elimination+-------------------------------------------------------------------------------++type Source m i a = S.ConduitT i a m ()+type Sink m a r = S.ConduitT a S.Void m r+type Pipe m a b = S.ConduitT a b m ()++{-# INLINE source #-}+source :: Monad m => Int -> Source m () Int+-- source n = sourceList [n..n+value]+source n = SL.unfoldM step n+ where+ step cnt =+ if cnt > n + value+ then return Nothing+ else return (Just (cnt, cnt + 1))++-------------------------------------------------------------------------------+-- Append+-------------------------------------------------------------------------------++{-# INLINE appendSource #-}+appendSource :: Monad m => Int -> Source m () Int+appendSource n = foldMap (S.yieldM . return) [n..n+value]++{-# INLINE runStream #-}+runStream :: Monad m => Sink m Int a -> Source m () Int -> m a+runStream t src = S.runConduit $ src S..| t++-------------------------------------------------------------------------------+-- Elimination+-------------------------------------------------------------------------------++eliminate :: Monad m => Sink m Int a -> Source m () Int -> m a+eliminate = runStream++toNull = eliminate $ S.sinkNull+toList = eliminate $ S.sinkList+foldl = eliminate $ S.foldl (+) 0+last = eliminate $ S.last++-------------------------------------------------------------------------------+-- Transformation+-------------------------------------------------------------------------------++{-# INLINE transform #-}+transform :: Monad m => Pipe m Int Int -> Source m () Int -> m ()+-- mapM_ is much more costly compared to sinkNull+--transform t = runStream (t S..| S.mapM_ (\_ -> return ()))+transform t = runStream (t S..| S.sinkNull)++scan = transform $ S.scanl (+) 0+map = transform $ S.map (+1)+mapM = transform $ S.mapM return+filterEven = transform $ S.filter even+filterAllOut = transform $ S.filter (> maxValue)+filterAllIn = transform $ S.filter (<= maxValue)+takeOne = transform $ S.take 1+takeAll = transform $ S.take maxValue+takeWhileTrue = transform $ S.takeWhile (<= maxValue)+dropAll = transform $ S.drop maxValue+dropWhileTrue = transform $ S.dropWhile (<= maxValue)++-------------------------------------------------------------------------------+-- Zipping and concat+-------------------------------------------------------------------------------++zip src = S.runConduit $+ ( S.getZipSource $ (,)+ <$> S.ZipSource src+ <*> S.ZipSource src) S..| S.sinkNull+concat = transform (S.map (replicate 3) S..| S.concat)++-------------------------------------------------------------------------------+-- Composition+-------------------------------------------------------------------------------++{-# INLINE compose #-}+compose :: Monad m => Pipe m Int Int -> Source m () Int -> m ()+compose f = transform $ (f S..| f S..| f S..| f)++composeMapM = compose (S.mapM return)+composeAllInFilters = compose (S.filter (<= maxValue))+composeAllOutFilters = compose (S.filter (> maxValue))+composeMapAllInFilter = compose (S.map (subtract 1) S..| S.filter (<= maxValue))++composeScaling :: Monad m => Int -> Source m () Int -> m ()+composeScaling m =+ case m of+ 1 -> transform f+ 2 -> transform (f S..| f)+ 3 -> transform (f S..| f S..| f)+ 4 -> transform (f S..| f S..| f S..| f)+ _ -> undefined+ where f = S.filter (<= maxValue)
+ Benchmarks/Drinkery.hs view
@@ -0,0 +1,125 @@+{-# LANGUAGE RankNTypes #-}+module Benchmarks.Drinkery where++import Benchmarks.Common (value, maxValue)+import Control.Monad (void)+import Prelude+ (Monad, Int, (+), ($), return, even, (>), (<=),+ subtract, undefined, replicate, (<$>), (<*>), fst, id)++import qualified Data.Drinkery as S+import qualified Data.Drinkery.Finite as S++-------------------------------------------------------------------------------+-- Benchmark ops+-------------------------------------------------------------------------------++{-# INLINE toNull #-}+{-# INLINE toList #-}+{-# INLINE foldl #-}+{-# INLINE last #-}+{-# INLINE scan #-}+{-# INLINE map #-}+{-# INLINE filterEven #-}+{-# INLINE mapM #-}+{-# INLINE filterAllOut #-}+{-# INLINE filterAllIn #-}+{-# INLINE takeOne #-}+{-# INLINE takeAll #-}+{-# INLINE takeWhileTrue #-}+{-# INLINE dropAll #-}+{-# INLINE dropWhileTrue #-}+{-# INLINE zip #-}+{-# INLINE concat #-}+{-# INLINE composeMapM #-}+{-# INLINE composeAllInFilters #-}+{-# INLINE composeAllOutFilters #-}+{-# INLINE composeMapAllInFilter #-}+toNull, toList, foldl, last, scan, map, filterEven, mapM, filterAllOut,+ filterAllIn, takeOne, takeAll, takeWhileTrue, dropAll, dropWhileTrue, zip,+ concat, composeMapM, composeAllInFilters, composeAllOutFilters,+ composeMapAllInFilter+ :: Monad m+ => Source m () Int -> m ()++-------------------------------------------------------------------------------+-- Stream generation and elimination+-------------------------------------------------------------------------------++type Source m i o = S.Source () o m+type Pipe m i o = S.Pipe i o m+type Sink m a r = S.Sink (S.Source () a) m r++{-# INLINE source #-}+source :: Monad m => Int -> Source m () Int+source n = S.tapListT $ S.sample [n .. n + value]++{-# INLINE runStream #-}+runStream :: Monad m => Pipe m Int o -> Source m () Int -> m ()+runStream t src = void $ src S.++& t S.$& S.drainFrom S.consume++-------------------------------------------------------------------------------+-- Elimination+-------------------------------------------------------------------------------++{-# INLINE eliminate #-}+eliminate :: Monad m => Sink m Int a -> Source m () Int -> m ()+eliminate s src = void $ src S.++& s++toNull = eliminate $ S.drainFrom S.consume+toList = eliminate S.drinkUp+foldl = eliminate $ S.foldlFrom' S.consume (+) 0+last = eliminate $ S.lastFrom S.consume++-------------------------------------------------------------------------------+-- Transformation+-------------------------------------------------------------------------------++{-# INLINE transform #-}+transform :: Monad m => Pipe m Int o -> Source m () Int -> m ()+transform = runStream++scan = transform $ S.scan (+) 0+map = transform $ S.map (+1)+mapM = transform $ S.traverse return+filterEven = transform $ S.filter even+filterAllOut = transform $ S.filter (> maxValue)+filterAllIn = transform $ S.filter (<= maxValue)+takeOne = transform $ S.take 1+takeAll = transform $ S.take maxValue+takeWhileTrue = transform $ S.takeWhile (<= maxValue)+dropAll = transform $ S.drop maxValue+dropWhileTrue = transform $ S.dropWhile (<= maxValue)++-------------------------------------------------------------------------------+-- Zipping and concat+-------------------------------------------------------------------------------++zip src = void+ $ S.unJoint ((,) <$> S.Joint src <*> S.Joint src)+ S.++& S.drainFrom (fst <$> S.consume)+concat = transform $ S.map (replicate 3) S.++$ S.concatMap id++-------------------------------------------------------------------------------+-- Composition+-------------------------------------------------------------------------------++{-# INLINE compose #-}+compose :: Monad m => (forall n. Monad n => Pipe n Int Int) -> Source m () Int -> m ()+compose f = transform (f S.++$ f S.++$ f S.++$ f)++composeMapM = compose (S.traverse return)+composeAllInFilters = compose (S.filter (<= maxValue))+composeAllOutFilters = compose (S.filter (> maxValue))+composeMapAllInFilter = compose (S.map (subtract 1) S.++$ S.filter (<= maxValue))++composeScaling :: Monad m => Int -> Source m () Int -> m ()+composeScaling m =+ case m of+ 1 -> transform f+ 2 -> transform (f S.++$ f)+ 3 -> transform (f S.++$ f S.++$ f)+ 4 -> transform (f S.++$ f S.++$ f S.++$ f)+ _ -> undefined+ where f :: Monad m => Pipe m Int Int+ f = S.filter (<= maxValue)
+ Benchmarks/List.hs view
@@ -0,0 +1,114 @@+-- |+-- Module : Benchmarks.List+-- Copyright : (c) 2018 Harendra Kumar+--+-- License : MIT+-- Maintainer : harendra.kumar@gmail.com++module Benchmarks.List where++import Benchmarks.Common (value, maxValue)+import Prelude (Int, (+), id, ($), (.), even, (>), (<=), subtract, undefined)++import qualified Data.List as S++-------------------------------------------------------------------------------+-- Benchmark ops+-------------------------------------------------------------------------------++{-# INLINE toNull #-}+{-# INLINE toList #-}+{-# INLINE foldl #-}+{-# INLINE last #-}+{-# INLINE scan #-}+{-# INLINE map #-}+{-# INLINE filterEven #-}+{-# INLINE mapM #-}+{-# INLINE filterAllOut #-}+{-# INLINE filterAllIn #-}+{-# INLINE takeOne #-}+{-# INLINE takeAll #-}+{-# INLINE takeWhileTrue #-}+{-# INLINE dropAll #-}+{-# INLINE dropWhileTrue #-}+{-# INLINE zip #-}+{-# INLINE concat #-}+{-# INLINE composeMapM #-}+{-# INLINE composeAllInFilters #-}+{-# INLINE composeAllOutFilters #-}+{-# INLINE composeMapAllInFilter #-}+toNull, toList, scan, map, filterEven, mapM, filterAllOut,+ filterAllIn, takeOne, takeAll, takeWhileTrue, dropAll, dropWhileTrue,+ concat, composeMapM, composeAllInFilters, composeAllOutFilters,+ composeMapAllInFilter+ :: [Int] -> [Int]++foldl :: [Int] -> Int+last :: [Int] -> Int+zip :: [Int] -> [(Int, Int)]++-------------------------------------------------------------------------------+-- Stream generation and elimination+-------------------------------------------------------------------------------++source :: Int -> [Int]+source v = [v..v+value]++-------------------------------------------------------------------------------+-- Elimination+-------------------------------------------------------------------------------++toNull = id+toList = id+foldl = S.foldl' (+) 0+last = S.last++-------------------------------------------------------------------------------+-- Transformation+-------------------------------------------------------------------------------++{-# INLINE transform #-}+transform :: [a] -> [a]+transform = id++scan = transform . S.scanl' (+) 0+map = transform . S.map (+1)+mapM = map+filterEven = transform . S.filter even+filterAllOut = transform . S.filter (> maxValue)+filterAllIn = transform . S.filter (<= maxValue)+takeOne = transform . S.take 1+takeAll = transform . S.take maxValue+takeWhileTrue = transform . S.takeWhile (<= maxValue)+dropAll = transform . S.drop maxValue+dropWhileTrue = transform . S.dropWhile (<= maxValue)++-------------------------------------------------------------------------------+-- Zipping and concat+-------------------------------------------------------------------------------++zip src = transform $ (S.zipWith (,) src src)+concat src = transform $ (S.concatMap (S.replicate 3) src)++-------------------------------------------------------------------------------+-- Composition+-------------------------------------------------------------------------------++{-# INLINE compose #-}+compose :: ([Int] -> [Int]) -> [Int] -> [Int]+compose f = transform . f . f . f . f++composeMapM = compose (S.map (+1))+composeAllInFilters = compose (S.filter (<= maxValue))+composeAllOutFilters = compose (S.filter (> maxValue))+composeMapAllInFilter = compose (S.filter (<= maxValue) . S.map (subtract 1))++composeScaling :: Int -> [Int] -> [Int]+composeScaling m =+ case m of+ 1 -> transform . f+ 2 -> transform . f . f+ 3 -> transform . f . f . f+ 4 -> transform . f . f . f . f+ _ -> undefined+ where f = S.filter (<= maxValue)
+ Benchmarks/Machines.hs view
@@ -0,0 +1,129 @@+-- |+-- Module : Benchmarks.Machines+-- Copyright : (c) 2018 Harendra Kumar+--+-- License : MIT+-- Maintainer : harendra.kumar@gmail.com++{-# LANGUAGE RankNTypes #-}+{-# OPTIONS_GHC -Wno-incomplete-patterns #-}+module Benchmarks.Machines where++import Benchmarks.Common (value, maxValue)+import Prelude+ (Monad, Int, (+), ($), return, even, (>), (<=),+ subtract, replicate, Maybe(..))++import qualified Data.Machine as S++-------------------------------------------------------------------------------+-- Benchmark ops+-------------------------------------------------------------------------------++{-# INLINE toNull #-}+{-# INLINE toList #-}+{-# INLINE foldl #-}+{-# INLINE last #-}+{-# INLINE scan #-}+{-# INLINE map #-}+{-# INLINE filterEven #-}+{-# INLINE mapM #-}+{-# INLINE filterAllOut #-}+{-# INLINE filterAllIn #-}+{-# INLINE takeOne #-}+{-# INLINE takeAll #-}+{-# INLINE takeWhileTrue #-}+{-# INLINE dropAll #-}+{-# INLINE dropWhileTrue #-}+{-# INLINE zip #-}+{-# INLINE concat #-}+{-# INLINE composeMapM #-}+{-# INLINE composeAllInFilters #-}+{-# INLINE composeAllOutFilters #-}+{-# INLINE composeMapAllInFilter #-}+toNull, foldl, last, scan, map, filterEven, mapM, filterAllOut,+ filterAllIn, takeOne, takeAll, takeWhileTrue, dropAll, dropWhileTrue, zip,+ concat, composeMapM, composeAllInFilters, composeAllOutFilters,+ composeMapAllInFilter+ :: Monad m+ => S.MachineT m k Int -> m ()++toList :: Monad m => S.MachineT m k Int -> m [Int]++-------------------------------------------------------------------------------+-- Stream generation and elimination+-------------------------------------------------------------------------------++type Source m o = S.SourceT m o+type Pipe m i o = S.ProcessT m i o++source :: Monad m => Int -> Source m Int+-- source n = S.source [n..n+value]+source n = S.unfoldT step n+ where+ step cnt =+ if cnt > n + value+ then return Nothing+ else return (Just (cnt, cnt + 1))++{-# INLINE runStream #-}+runStream :: Monad m => Pipe m Int o -> S.MachineT m k Int -> m ()+runStream t src = S.runT_ $ src S.~> t++-------------------------------------------------------------------------------+-- Elimination+-------------------------------------------------------------------------------++toNull = S.runT_+toList = S.runT+foldl = runStream $ S.fold (+) 0+last = runStream $ S.final++-------------------------------------------------------------------------------+-- Transformation+-------------------------------------------------------------------------------++{-# INLINE transform #-}+transform :: Monad m => Pipe m Int o -> S.MachineT m k Int -> m ()+transform = runStream++scan = transform $ S.scan (+) 0+map = transform $ S.mapping (+1)+mapM = transform $ S.autoM return+filterEven = transform $ S.filtered even+filterAllOut = transform $ S.filtered (> maxValue)+filterAllIn = transform $ S.filtered (<= maxValue)+takeOne = transform $ S.taking 1+takeAll = transform $ S.taking maxValue+takeWhileTrue = transform $ S.takingWhile (<= maxValue)+dropAll = transform $ S.dropping maxValue+dropWhileTrue = transform $ S.droppingWhile (<= maxValue)++-------------------------------------------------------------------------------+-- Zipping and concat+-------------------------------------------------------------------------------++zip _src = S.runT_ (S.capT (source 10) (source 20) S.zipping)+concat = transform (S.mapping (replicate 3) S.~> S.asParts)++-------------------------------------------------------------------------------+-- Composition+-------------------------------------------------------------------------------++compose :: Monad m => Pipe m Int Int -> S.MachineT m k Int -> m ()+compose f = transform $ (f S.~> f S.~> f S.~> f)++composeMapM = compose (S.autoM return)+composeAllInFilters = compose (S.filtered (<= maxValue))+composeAllOutFilters = compose (S.filtered (> maxValue))+composeMapAllInFilter = compose (S.mapping (subtract 1) S.~> S.filtered (<= maxValue))++composeScaling :: Monad m => Int -> Source m Int -> m ()+composeScaling m =+ case m of+ 1 -> transform f+ 2 -> transform (f S.~> f)+ 3 -> transform (f S.~> f S.~> f)+ 4 -> transform (f S.~> f S.~> f S.~> f)+ -- _ -> undefined+ where f = S.filtered (<= maxValue)
+ Benchmarks/Pipes.hs view
@@ -0,0 +1,140 @@+-- |+-- Module : Benchmarks.Pipes+-- Copyright : (c) 2018 Harendra Kumar+--+-- License : MIT+-- Maintainer : harendra.kumar@gmail.com++{-# LANGUAGE RankNTypes #-}++module Benchmarks.Pipes where++import Benchmarks.Common (value, maxValue)+import Data.Void (Void)+import Prelude+ (Monad, Int, (+), ($), id, return, even, (>), (<=),+ subtract, undefined, replicate, Maybe, Either(..), foldMap)++import qualified Pipes as S+import qualified Pipes.Prelude as S++-------------------------------------------------------------------------------+-- Benchmark ops+-------------------------------------------------------------------------------++{-# INLINE toNull #-}+{-# INLINE toList #-}+{-# INLINE foldl #-}+{-# INLINE last #-}+{-# INLINE scan #-}+{-# INLINE map #-}+{-# INLINE filterEven #-}+{-# INLINE mapM #-}+{-# INLINE filterAllOut #-}+{-# INLINE filterAllIn #-}+{-# INLINE takeOne #-}+{-# INLINE takeAll #-}+{-# INLINE takeWhileTrue #-}+{-# INLINE dropAll #-}+{-# INLINE dropWhileTrue #-}+{-# INLINE zip #-}+{-# INLINE concat #-}+{-# INLINE composeMapM #-}+{-# INLINE composeAllInFilters #-}+{-# INLINE composeAllOutFilters #-}+{-# INLINE composeMapAllInFilter #-}+toNull, scan, map, filterEven, mapM, filterAllOut,+ filterAllIn, takeOne, takeAll, takeWhileTrue, dropAll, dropWhileTrue, zip,+ concat, composeMapM, composeAllInFilters, composeAllOutFilters,+ composeMapAllInFilter+ :: Monad m+ => Source m () Int -> m ()++toList :: Monad m => Source m () Int -> m [Int]+foldl :: Monad m => Source m () Int -> m Int+last :: Monad m => Source m () Int -> m (Maybe Int)++-------------------------------------------------------------------------------+-- Stream generation and elimination+-------------------------------------------------------------------------------++type Source m i o = S.Producer o m i+type Sink m i r = S.Proxy () i () Void m r+type Pipe m i o = S.Proxy () i () o m ()++{-# INLINE source #-}+source :: Monad m => Int -> Source m () Int+-- source n = S.each [n..n+value]+source n = S.unfoldr step n+ where+ step cnt =+ if cnt > n + value+ then return $ Left ()+ else return (Right (cnt, cnt + 1))++-------------------------------------------------------------------------------+-- Append+-------------------------------------------------------------------------------++{-# INLINE appendSource #-}+appendSource :: Monad m => Int -> Source m () Int+appendSource n = foldMap S.yield [n..n+value]++-------------------------------------------------------------------------------+-- Elimination+-------------------------------------------------------------------------------++toNull src = S.runEffect $ S.for src S.discard+toList = S.toListM+foldl = S.fold (+) 0 id+last = S.last++-------------------------------------------------------------------------------+-- Transformation+-------------------------------------------------------------------------------++{-# INLINE transform #-}+transform :: Monad m => Pipe m Int Int -> Source m () Int -> m ()+transform t src = S.runEffect $ S.for (src S.>-> t) S.discard++scan = transform $ S.scan (+) 0 id+map = transform $ S.map (+1)+mapM = transform $ S.mapM return+filterEven = transform $ S.filter even+filterAllOut = transform $ S.filter (> maxValue)+filterAllIn = transform $ S.filter (<= maxValue)+takeOne = transform $ S.take 1+takeAll = transform $ S.take maxValue+takeWhileTrue = transform $ S.takeWhile (<= maxValue)+dropAll = transform $ S.drop maxValue+dropWhileTrue = transform $ S.dropWhile (<= maxValue)++-------------------------------------------------------------------------------+-- Zipping and concat+-------------------------------------------------------------------------------++zip src = S.runEffect $ S.for (S.zip src src) S.discard+concat = transform (S.map (replicate 3) S.>-> S.concat)++-------------------------------------------------------------------------------+-- Composition+-------------------------------------------------------------------------------++{-# INLINE compose #-}+compose :: Monad m => Pipe m Int Int -> Source m () Int -> m ()+compose f = transform $ (f S.>-> f S.>-> f S.>-> f)++composeMapM = compose (S.mapM return)+composeAllInFilters = compose (S.filter (<= maxValue))+composeAllOutFilters = compose (S.filter (> maxValue))+composeMapAllInFilter = compose (S.map (subtract 1) S.>-> S.filter (<= maxValue))++composeScaling :: Monad m => Int -> Source m () Int -> m ()+composeScaling m =+ case m of+ 1 -> transform f+ 2 -> transform (f S.>-> f)+ 3 -> transform (f S.>-> f S.>-> f)+ 4 -> transform (f S.>-> f S.>-> f S.>-> f)+ _ -> undefined+ where f = S.filter (<= maxValue)
+ Benchmarks/Streaming.hs view
@@ -0,0 +1,147 @@+-- |+-- Module : Benchmarks.Streaming+-- Copyright : (c) 2018 Harendra Kumar+--+-- License : MIT+-- Maintainer : harendra.kumar@gmail.com++{-# OPTIONS_GHC -fno-warn-orphans #-}++module Benchmarks.Streaming where++import Benchmarks.Common (value, maxValue)+import Control.DeepSeq (NFData)+import Prelude+ (Monad, Int, (+), id, ($), (.), return, even, (>), (<=),+ subtract, undefined, Maybe, Either(..), foldMap)+--import Prelude (replicate)++import qualified Streaming.Prelude as S++-------------------------------------------------------------------------------+-- Benchmark ops+-------------------------------------------------------------------------------++{-# INLINE toNull #-}+{-# INLINE toList #-}+{-# INLINE foldl #-}+{-# INLINE last #-}+{-# INLINE scan #-}+{-# INLINE map #-}+{-# INLINE filterEven #-}+{-# INLINE mapM #-}+{-# INLINE filterAllOut #-}+{-# INLINE filterAllIn #-}+{-# INLINE takeOne #-}+{-# INLINE takeAll #-}+{-# INLINE takeWhileTrue #-}+{-# INLINE dropAll #-}+{-# INLINE dropWhileTrue #-}+{-# INLINE zip #-}+{-# INLINE concat #-}+{-# INLINE composeMapM #-}+{-# INLINE composeAllInFilters #-}+{-# INLINE composeAllOutFilters #-}+{-# INLINE composeMapAllInFilter #-}+toNull, scan, map, filterEven, mapM, filterAllOut,+ filterAllIn, takeOne, takeAll, takeWhileTrue, dropAll, dropWhileTrue, zip,+ concat, composeMapM, composeAllInFilters, composeAllOutFilters,+ composeMapAllInFilter+ :: Monad m+ => Stream m Int -> m ()++toList :: Monad m => Stream m Int -> m (S.Of [Int] ())+foldl :: Monad m => Stream m Int -> m (S.Of Int ())+last :: Monad m => Stream m Int -> m (S.Of (Maybe Int) ())++-------------------------------------------------------------------------------+-- Stream generation and elimination+-------------------------------------------------------------------------------++-- Orphan instance to use nfIO on streaming+instance (NFData a, NFData b) => NFData (S.Of a b)++type Stream m a = S.Stream (S.Of a) m ()++{-# INLINE source #-}+source :: Monad m => Int -> Stream m Int+-- source n = S.each [n..n+value]+source n = S.unfoldr step n+ where+ step cnt =+ if cnt > n + value+ then return $ Left ()+ else return (Right (cnt, cnt + 1))++-------------------------------------------------------------------------------+-- Append+-------------------------------------------------------------------------------++{-# INLINE appendSource #-}+appendSource :: Monad m => Int -> Stream m Int+appendSource n = foldMap S.yield [n..n+value]++{-# INLINE runStream #-}+runStream :: Monad m => Stream m a -> m ()+runStream = S.mapM_ (\_ -> return ())++-------------------------------------------------------------------------------+-- Elimination+-------------------------------------------------------------------------------++toNull = runStream+toList = S.toList+foldl = S.fold (+) 0 id+last = S.last++-------------------------------------------------------------------------------+-- Transformation+-------------------------------------------------------------------------------++{-# INLINE transform #-}+transform :: Monad m => Stream m a -> m ()+transform = runStream++scan = transform . S.scan (+) 0 id+map = transform . S.map (+1)+mapM = transform . S.mapM return+filterEven = transform . S.filter even+filterAllOut = transform . S.filter (> maxValue)+filterAllIn = transform . S.filter (<= maxValue)+takeOne = transform . S.take 1+takeAll = transform . S.take maxValue+takeWhileTrue = transform . S.takeWhile (<= maxValue)+dropAll = transform . S.drop maxValue+dropWhileTrue = transform . S.dropWhile (<= maxValue)++-------------------------------------------------------------------------------+-- Zipping and concat+-------------------------------------------------------------------------------++zip src = runStream $ (S.zip src src)+concat _src = return ()+ -- it just hangs with 100% CPU usage+ -- runStream $ (S.concat $ S.map (replicate 3) (source n))++-------------------------------------------------------------------------------+-- Composition+-------------------------------------------------------------------------------++{-# INLINE compose #-}+compose :: Monad m => (Stream m Int -> Stream m Int) -> Stream m Int -> m ()+compose f = transform . f . f . f . f++composeMapM = compose (S.mapM return)+composeAllInFilters = compose (S.filter (<= maxValue))+composeAllOutFilters = compose (S.filter (> maxValue))+composeMapAllInFilter = compose (S.filter (<= maxValue) . S.map (subtract 1))++composeScaling :: Monad m => Int -> Stream m Int -> m ()+composeScaling m =+ case m of+ 1 -> transform . f+ 2 -> transform . f . f+ 3 -> transform . f . f . f+ 4 -> transform . f . f . f . f+ _ -> undefined+ where f = S.filter (<= maxValue)
+ Benchmarks/Streamly.hs view
@@ -0,0 +1,167 @@+-- |+-- Module : Benchmarks.Streamly+-- Copyright : (c) 2018 Harendra Kumar+--+-- License : MIT+-- Maintainer : harendra.kumar@gmail.com++{-# LANGUAGE FlexibleContexts #-}+module Benchmarks.Streamly where++import Benchmarks.Common (value, maxValue)+import Prelude+ (Monad, Int, (+), ($), (.), return, fmap, even, (>), (<=),+ subtract, undefined, Maybe(..), foldMap)+import qualified Prelude as P++import qualified Streamly as S+import qualified Streamly.Prelude as S++-------------------------------------------------------------------------------+-- Benchmark ops+-------------------------------------------------------------------------------++{-# INLINE toNull #-}+{-# INLINE toList #-}+{-# INLINE foldl #-}+{-# INLINE last #-}+{-# INLINE scan #-}+{-# INLINE map #-}+{-# INLINE filterEven #-}+{-# INLINE mapM #-}+{-# INLINE filterAllOut #-}+{-# INLINE filterAllIn #-}+{-# INLINE takeOne #-}+{-# INLINE takeAll #-}+{-# INLINE takeWhileTrue #-}+{-# INLINE dropAll #-}+{-# INLINE dropWhileTrue #-}+{-# INLINE zip #-}+{-# INLINE concat #-}+{-# INLINE composeMapM #-}+{-# INLINE composeAllInFilters #-}+{-# INLINE composeAllOutFilters #-}+{-# INLINE composeMapAllInFilter #-}+toNull, scan, map, filterEven, filterAllOut,+ filterAllIn, takeOne, takeAll, takeWhileTrue, dropAll, dropWhileTrue, zip,+ concat, composeAllInFilters, composeAllOutFilters,+ composeMapAllInFilter+ :: Monad m+ => Stream m Int -> m ()++mapM, composeMapM :: S.MonadAsync m => Stream m Int -> m ()+toList :: Monad m => Stream m Int -> m [Int]+foldl :: Monad m => Stream m Int -> m Int+last :: Monad m => Stream m Int -> m (Maybe Int)++-------------------------------------------------------------------------------+-- Stream generation and elimination+-------------------------------------------------------------------------------++type Stream m a = S.SerialT m a++{-# INLINE source #-}+source :: S.MonadAsync m => Int -> Stream m Int+-- source n = S.fromFoldable [n..n+value]+source n = S.unfoldrM step n+ where+ step cnt =+ if cnt > n + value+ then return Nothing+ else return (Just (cnt, cnt + 1))+ {-+source n = S.unfoldr step n+ where+ step cnt =+ if cnt > n + value+ then Nothing+ else (Just (cnt, cnt + 1))+ -}++{-# INLINE sourceN #-}+sourceN :: S.MonadAsync m => Int -> Int -> Stream m Int+sourceN count begin = S.unfoldrM step begin+ where+ step i =+ if i > begin + count+ then return Nothing+ else return (Just (i, i + 1))++-------------------------------------------------------------------------------+-- Append+-------------------------------------------------------------------------------++{-# INLINE appendSource #-}+appendSource :: Monad m => Int -> Stream m Int+appendSource n = foldMap (S.yieldM . return) [n..n+value]++{-# INLINE mapMSource #-}+mapMSource :: S.MonadAsync m => Int -> Stream m Int+mapMSource n = f 100000 (sourceN 10 n)+ where+ f :: S.MonadAsync m => Int -> Stream m Int -> Stream m Int+ f 0 m = S.mapM return m+ f x m = S.mapM return (f (x P.- 1) m)++{-# INLINE runStream #-}+runStream :: Monad m => Stream m a -> m ()+runStream = S.runStream++-------------------------------------------------------------------------------+-- Elimination+-------------------------------------------------------------------------------++toNull = runStream+toList = S.toList+foldl = S.foldl' (+) 0+last = S.last++-------------------------------------------------------------------------------+-- Transformation+-------------------------------------------------------------------------------++{-# INLINE transform #-}+transform :: Monad m => Stream m a -> m ()+transform = runStream++scan = transform . S.scanl' (+) 0+map = transform . fmap (+1)+mapM = transform . S.mapM return+filterEven = transform . S.filter even+filterAllOut = transform . S.filter (> maxValue)+filterAllIn = transform . S.filter (<= maxValue)+takeOne = transform . S.take 1+takeAll = transform . S.take maxValue+takeWhileTrue = transform . S.takeWhile (<= maxValue)+dropAll = transform . S.drop maxValue+dropWhileTrue = transform . S.dropWhile (<= maxValue)++-------------------------------------------------------------------------------+-- Zipping and concat+-------------------------------------------------------------------------------++zip src = transform $ (S.zipWith (,) src src)+concat _n = return ()++-------------------------------------------------------------------------------+-- Composition+-------------------------------------------------------------------------------++{-# INLINE compose #-}+compose :: Monad m => (Stream m Int -> Stream m Int) -> Stream m Int -> m ()+compose f = transform . f . f . f . f++composeMapM = compose (S.mapM return)+composeAllInFilters = compose (S.filter (<= maxValue))+composeAllOutFilters = compose (S.filter (> maxValue))+composeMapAllInFilter = compose (S.filter (<= maxValue) . fmap (subtract 1))++composeScaling :: Monad m => Int -> Stream m Int -> m ()+composeScaling m =+ case m of+ 1 -> transform . f+ 2 -> transform . f . f+ 3 -> transform . f . f . f+ 4 -> transform . f . f . f . f+ _ -> undefined+ where f = S.filter (<= maxValue)
+ Benchmarks/Vector.hs view
@@ -0,0 +1,165 @@+-- |+-- Module : Benchmarks.Vector+-- Copyright : (c) 2018 Harendra Kumar+-- (c) 2018 Philipp Schuster+--+-- License : MIT+-- Maintainer : harendra.kumar@gmail.com++module Benchmarks.Vector where++import Benchmarks.Common (value, maxValue)+import Prelude+ (Monad, Int, (+), ($), (.), return, even, (>), (<=),+ subtract, undefined, replicate, Maybe(..))+import qualified Prelude as P++import qualified Data.Vector.Fusion.Stream.Monadic as S++-------------------------------------------------------------------------------+-- Benchmark ops+-------------------------------------------------------------------------------++{-# INLINE toNull #-}+{-# INLINE toList #-}+{-# INLINE foldl #-}+{-# INLINE last #-}+{-# INLINE scan #-}+{-# INLINE map #-}+{-# INLINE filterEven #-}+{-# INLINE mapM #-}+{-# INLINE filterAllOut #-}+{-# INLINE filterAllIn #-}+{-# INLINE takeOne #-}+{-# INLINE takeAll #-}+{-# INLINE takeWhileTrue #-}+{-# INLINE dropAll #-}+{-# INLINE dropWhileTrue #-}+{-# INLINE zip #-}+{-# INLINE concat #-}+{-# INLINE composeMapM #-}+{-# INLINE composeAllInFilters #-}+{-# INLINE composeAllOutFilters #-}+{-# INLINE composeMapAllInFilter #-}+toNull, scan, map, filterEven, mapM, filterAllOut,+ filterAllIn, takeOne, takeAll, takeWhileTrue, dropAll, dropWhileTrue, zip,+ concat, composeMapM, composeAllInFilters, composeAllOutFilters,+ composeMapAllInFilter+ :: Monad m+ => Stream m Int -> m ()++toList :: Monad m => Stream m Int -> m [Int]+foldl :: Monad m => Stream m Int -> m Int+last :: Monad m => Stream m Int -> m Int++-------------------------------------------------------------------------------+-- Stream generation and elimination+-------------------------------------------------------------------------------++type Stream m a = S.Stream m a++{-# INLINE source #-}+source :: Monad m => Int -> Stream m Int+--source n = S.fromList [n..n+value]+source n = S.unfoldrM step n+ where+ step cnt =+ if cnt > n + value+ then return Nothing+ else return (Just (cnt, cnt + 1))+ {-+source n = S.unfoldr step n+ where+ step cnt =+ if cnt > n + value+ then Nothing+ else (Just (cnt, cnt + 1))+ -}++{-# INLINE sourceN #-}+sourceN :: Monad m => Int -> Int -> Stream m Int+sourceN count begin = S.unfoldrM step begin+ where+ step i =+ if i > begin + count+ then return Nothing+ else return (Just (i, i + 1))++-------------------------------------------------------------------------------+-- Append+-------------------------------------------------------------------------------++{-# INLINE appendSource #-}+appendSource :: Monad m => Int -> Stream m Int+appendSource n = P.foldr (S.++) S.empty (P.map S.singleton [n..n+value])++{-# INLINE mapMSource #-}+mapMSource :: Monad m => Int -> Stream m Int+mapMSource n = f 100000 (sourceN 10 n)+ where+ f :: Monad m => Int -> Stream m Int -> Stream m Int+ f 0 m = S.mapM return m+ f x m = S.mapM return (f (x P.- 1) m)++{-# INLINE runStream #-}+runStream :: Monad m => Stream m a -> m ()+runStream = S.mapM_ (\_ -> return ())++-------------------------------------------------------------------------------+-- Elimination+-------------------------------------------------------------------------------++toNull = runStream+toList = S.toList+foldl = S.foldl' (+) 0+last = S.last++-------------------------------------------------------------------------------+-- Transformation+-------------------------------------------------------------------------------++{-# INLINE transform #-}+transform :: Monad m => Stream m a -> m ()+transform = runStream++scan = transform . S.prescanl' (+) 0+map = transform . S.map (+1)+mapM = transform . S.mapM return+filterEven = transform . S.filter even+filterAllOut = transform . S.filter (> maxValue)+filterAllIn = transform . S.filter (<= maxValue)+takeOne = transform . S.take 1+takeAll = transform . S.take maxValue+takeWhileTrue = transform . S.takeWhile (<= maxValue)+dropAll = transform . S.drop maxValue+dropWhileTrue = transform . S.dropWhile (<= maxValue)++-------------------------------------------------------------------------------+-- Zipping and concat+-------------------------------------------------------------------------------++zip src = transform $ (S.zipWith (,) src src)+concat src = transform $ (S.concatMap (S.fromList . replicate 3) src)++-------------------------------------------------------------------------------+-- Composition+-------------------------------------------------------------------------------++{-# INLINE compose #-}+compose :: Monad m => (Stream m Int -> Stream m Int) -> Stream m Int -> m ()+compose f = transform . f . f . f . f++composeMapM = compose (S.mapM return)+composeAllInFilters = compose (S.filter (<= maxValue))+composeAllOutFilters = compose (S.filter (> maxValue))+composeMapAllInFilter = compose (S.filter (<= maxValue) . S.map (subtract 1))++composeScaling :: Monad m => Int -> Stream m Int -> m ()+composeScaling n =+ case n of+ 1 -> transform . f+ 2 -> transform . f . f+ 3 -> transform . f . f . f+ 4 -> transform . f . f . f . f+ _ -> undefined+ where f = S.filter (<= maxValue)
+ Benchmarks/VectorPure.hs view
@@ -0,0 +1,116 @@+-- |+-- Module : Benchmarks.VectorPure+-- Copyright : (c) 2018 Harendra Kumar+--+-- License : MIT+-- Maintainer : harendra.kumar@gmail.com++module Benchmarks.VectorPure where++import Benchmarks.Common (value, maxValue)+import Prelude (Int, (+), id, ($), (.), even, (>), (<=), subtract, undefined)++import qualified Data.Vector as S++-------------------------------------------------------------------------------+-- Benchmark ops+-------------------------------------------------------------------------------++{-# INLINE toNull #-}+{-# INLINE toList #-}+{-# INLINE foldl #-}+{-# INLINE last #-}+{-# INLINE scan #-}+{-# INLINE map #-}+{-# INLINE filterEven #-}+{-# INLINE mapM #-}+{-# INLINE filterAllOut #-}+{-# INLINE filterAllIn #-}+{-# INLINE takeOne #-}+{-# INLINE takeAll #-}+{-# INLINE takeWhileTrue #-}+{-# INLINE dropAll #-}+{-# INLINE dropWhileTrue #-}+{-# INLINE zip #-}+{-# INLINE concat #-}+{-# INLINE composeMapM #-}+{-# INLINE composeAllInFilters #-}+{-# INLINE composeAllOutFilters #-}+{-# INLINE composeMapAllInFilter #-}+scan, map, filterEven, mapM, filterAllOut,+ filterAllIn, takeOne, takeAll, takeWhileTrue, dropAll, dropWhileTrue,+ concat, composeMapM, composeAllInFilters, composeAllOutFilters,+ composeMapAllInFilter+ :: S.Vector Int -> S.Vector Int++toNull :: S.Vector Int -> [Int]+toList :: S.Vector Int -> [Int]+foldl :: S.Vector Int -> Int+last :: S.Vector Int -> Int+zip :: S.Vector Int -> S.Vector (Int, Int)++-------------------------------------------------------------------------------+-- Stream generation and elimination+-------------------------------------------------------------------------------++source :: Int -> S.Vector Int+source v = S.fromList [v..v+value]++-------------------------------------------------------------------------------+-- Elimination+-------------------------------------------------------------------------------++toNull = S.toList+toList = S.toList+foldl = S.foldl' (+) 0+last = S.last++-------------------------------------------------------------------------------+-- Transformation+-------------------------------------------------------------------------------++{-# INLINE transform #-}+transform :: S.Vector a -> S.Vector a+transform = id++scan = transform . S.scanl' (+) 0+map = transform . S.map (+1)+mapM = map+filterEven = transform . S.filter even+filterAllOut = transform . S.filter (> maxValue)+filterAllIn = transform . S.filter (<= maxValue)+takeOne = transform . S.take 1+takeAll = transform . S.take maxValue+takeWhileTrue = transform . S.takeWhile (<= maxValue)+dropAll = transform . S.drop maxValue+dropWhileTrue = transform . S.dropWhile (<= maxValue)++-------------------------------------------------------------------------------+-- Zipping and concat+-------------------------------------------------------------------------------++zip src = transform $ (S.zipWith (,) src src)+concat src = transform $ (S.concatMap (S.replicate 3) src)++-------------------------------------------------------------------------------+-- Composition+-------------------------------------------------------------------------------++{-# INLINE compose #-}+compose :: (S.Vector Int -> S.Vector Int) -> S.Vector Int -> S.Vector Int+compose f = transform . f . f . f . f++composeMapM = compose (S.map (+1))+composeAllInFilters = compose (S.filter (<= maxValue))+composeAllOutFilters = compose (S.filter (> maxValue))+composeMapAllInFilter = compose (S.filter (<= maxValue) . S.map (subtract 1))++composeScaling :: Int -> S.Vector Int -> S.Vector Int+composeScaling m =+ case m of+ 1 -> transform . f+ 2 -> transform . f . f+ 3 -> transform . f . f . f+ 4 -> transform . f . f . f . f+ _ -> undefined+ where f = S.filter (<= maxValue)
+ Changelog.md view
@@ -0,0 +1,24 @@+## 0.2.0++* Added benchmarks for pure lists+* Added benchmarks for pure `vector`+* Added benchmarks for `vector` monadic streaming library+* Added `drinkery` streaming library+* The code is modular now, package specific ops for each benchmarked package+ are contained in a separate own module. It is much easier to add a new+ package now.+* The benchmarking code now works for `IO` as well as `Identity` monad.+* Used the same stream generation method for all libraries for a fair+ comparison.+* Use a monadic API (`unfoldrM`) for generating the stream.+* conduit-1.3.0 has a performance issue with `mapM_`. Avoided using `mapM_` and+ used `sinkNull` instead. See https://github.com/snoyberg/conduit/issues/363.+ This workaround improves the performance of all conduit benchmarks that drain+ the stream.+* pipes also had an issue similar to that of conduit. The code was using+ `mapM_` which was very inefficient, used `discard` instead and got a+ significant boost in numbers.++## 0.1.0++* Initial release
Charts.hs view
@@ -1,200 +1,148 @@-import Control.Arrow (second)-import Data.Char (isSpace)-import Data.List.Split (splitOn)-import Data.Maybe (fromMaybe, catMaybes, fromJust)-import Debug.Trace (trace)-import System.Directory (createDirectoryIfMissing)-import System.Environment (getArgs)-import System.Process.Typed (readProcess_)-import Text.CSV (CSV, parseCSVFromFile)--import qualified Data.Text.Lazy.Encoding as T-import qualified Data.Text.Lazy as T+{-# LANGUAGE FlexibleContexts #-} -import Graphics.Rendering.Chart.Easy-import Graphics.Rendering.Chart.Backend.Diagrams+module Main where ----------------------------------------------------------------------------------- Configurable stuff--------------------------------------------------------------------------------+import Data.Char (isSpace)+import Data.List.Split (splitOn)+import Data.Maybe (catMaybes)+import System.Exit (ExitCode(..))+import System.Process.Typed (readProcess)+import BenchGraph (bgraph, defaultConfig, Config(..), ComparisonStyle(..))+import WithCli (withCli) -outputDir :: String-outputDir = "charts"+import Data.List -packages :: [String]-packages = ["streamly", "streaming", "pipes", "conduit", "machines", "vector"]+import qualified Data.Text.Lazy as T+import qualified Data.Text.Lazy.Encoding as T -- pairs of benchmark group titles and corresponding list of benchmark -- prefixes i.e. without the package name at the end.-bmGroups :: [(String, [String])]-bmGroups =+charts :: [(String, [String])]+charts = [ -- Operations are listed in increasing cost order- ( "All Operations at a Glance (Shorter is Faster)"+ {-+ ( "Key Operations" , [- -- "filtering/take-one"- "elimination/toNull"- , "filtering/drop-all"- , "elimination/last"- , "elimination/fold"-- , "filtering/filter-all-out"- , "filtering/dropWhile-true"- , "filtering/take-all"- , "filtering/takeWhile-true"- , "transformation/map"- , "filtering/filter-all-in"- , "filtering/filter-even"-- , "elimination/scan"+ "elimination/fold" , "transformation/mapM"- , "zip"-- , "elimination/toList"- , "elimination/concat"+ , "filtering/filter-even"+ , "zip" ] )- , ( "Discarding and Folding (Shorter is Faster)"+ , -} ( "Append Operation"+ , [ "append"+ ]+ )+ , ( "Key Operations" , [- -- "filtering/take-one"- "elimination/toNull"+ "elimination/drain" , "filtering/drop-all"+ -- , "filtering/dropWhile-true"+ -- , "filtering/filter-all-out" , "elimination/last" , "elimination/fold"- ]- )- , ( "Pure Transformation and Filtering (Shorter is Faster)"- , [- "filtering/filter-all-out"- , "filtering/dropWhile-true"- , "filtering/take-all"- , "filtering/takeWhile-true"+ -- "filtering/take-one" , "transformation/map"- , "filtering/filter-all-in"+ , "filtering/take-all"+ --, "filtering/takeWhile-true"+ -- , "filtering/filter-all-in" , "filtering/filter-even"- , "elimination/scan"- ]- )- , ( "Monadic Transformation (Shorter is Faster)"- , [- "transformation/mapM"- ]- )- , ( "Folding to List (Shorter is Faster)"- , [- "elimination/toList"+ , "transformation/scan"+ , "transformation/mapM"+ , "zip"+ -- , "transformation/concat" ] )- , ( "Zipping and Concating Streams (Shorter is Faster)"- , [ "zip"- , "elimination/concat"+ , ( "toList Operation"+ , [ "elimination/toList" ] )- , ( "Composing Pipeline Stages (Shorter is Faster)"- , [- "compose/all-out-filters"+ , ( "Composed Operations: 4 times"+ , [ "compose/mapM" , "compose/all-in-filters" , "compose/map-with-all-in-filter"- , "compose/mapM" ] ) ] ------------------------------------------------------------------------------- --- "values" has results for each package for each title in bmTitles-genGroupGraph :: String -> [String] -> [(String, [Maybe Double])] -> IO ()-genGroupGraph bmGroupName bmTitles values =- toFile def (outputDir- ++ "/"- -- links in README.rst eat up the space so we match the same- ++ (filter (not . isSpace) (takeWhile (/= '(') bmGroupName))- ++ ".svg") $ do- layout_title .= bmGroupName- layout_title_style . font_size .= 25- layout_x_axis . laxis_generate .= autoIndexAxis (map fst values)- layout_x_axis . laxis_style . axis_label_style . font_size .= 12-- -- layout_y_axis . laxis_override .= axisGridAtTicks- let modifyLabels ad = ad {- _axis_labels = map (map (second (++ " ms"))) (_axis_labels ad)- }- layout_y_axis . laxis_override .= modifyLabels- -- XXX We are mapping a missing value to 0, can we label it missing- -- instead?- let modifyVal x = map ((*1000) . fromMaybe 0) (snd x)- plot $ fmap plotBars $ bars bmTitles (addIndexes (map modifyVal values))---- Given a package name (e.g. streaming) and benchmark prefixes (e.g.--- [elimination/null, elimination/toList]) get the corresponding results e.g.--- [8.1 ms, 5.4 ms]. The corresponding result file entries will have--- elimination/null/streaming etc. as the names of the entries.-getResultsForPackage :: CSV -> String -> [String] -> [Maybe Double]-getResultsForPackage csvData pkgname bmPrefixes =- map (getBenchmarkMean csvData)- $ map (++ "/" ++ pkgname) bmPrefixes-- where+-- returns [(packagename, version)]+getPkgVersions :: [String] -> IO [(String, String)]+getPkgVersions packages = do+ (ecode, out, _) <- readProcess "stack --system-ghc list-dependencies --bench" - getBenchmarkMean entries bmname =- case filter ((== bmname) . head) entries of- [] -> trace- ("Warning! Benchmark [" ++ bmname ++"] not found in csv data")- Nothing- xs -> Just (read ((last xs) !! 1))+ case ecode of+ ExitSuccess -> do+ -- Get our streaming packages and their versions+ let match [] = Nothing+ match (_ : []) = Nothing+ match (x : y : _) =+ case elem x packages of+ False -> Nothing+ True -> Just (x, y) -genOneGraph :: CSV -> [(String, String)] -> (String, [String]) -> IO ()-genOneGraph csvData pkginfo (bmGroupTitle, prefixes) =- genGroupGraph bmGroupTitle bmTitles bmResults+ in return+ $ catMaybes+ $ map match+ $ map words (lines (T.unpack $ T.decodeUtf8 out))+ ExitFailure _ -> do+ putStrLn $ "Warning! Cannot determine package versions, "+ ++ "the 'stack list-dependencies' command failed."+ return [] - where+-- suffix versions to packages+suffixVersion :: [(String, String)] -> String -> String+suffixVersion pkginfo p =+ case lookup p pkginfo of+ Nothing -> p+ Just v -> p ++ "-" ++ v - bmTitles = map (last . splitOn "/" ) prefixes+createCharts :: String -> String -> Bool -> IO ()+createCharts input pkgList delta = do+ let packages = splitOn "," pkgList+ let pkgInfo = []+ -- pkgInfo <- getPkgVersions+ let cfg (title, prefixes) = defaultConfig+ { chartTitle = Just title+ , outputDir = "charts"+ , comparisonStyle = if delta then CompareDelta else CompareFull+ , classifyBenchmark = \bm ->+ case any (`isPrefixOf` bm) prefixes of+ True ->+ let xs = reverse (splitOn "/" bm)+ grp = xs !! 0+ bench = xs !! 1+ in case grp `elem` packages of+ True -> Just (suffixVersion pkgInfo grp, bench)+ False -> Nothing+ False -> Nothing+ , sortBenchmarks = \bs ->+ let i = intersect (map (last . splitOn "/") prefixes) bs+ in i ++ (bs \\ i)+ , sortBenchGroups = \gs ->+ let i = intersect (map (suffixVersion pkgInfo) packages) gs+ in i ++ (gs \\ i)+ } - pkgName = fst- pkgVersion = snd- pkgNameWithVersion pkgInfo = pkgName pkgInfo ++ "-" ++ pkgVersion pkgInfo- pkgGetResults pkgInfo =- let vals = getResultsForPackage csvData (pkgName pkgInfo) prefixes- in (pkgNameWithVersion pkgInfo, vals)+ -- links in README.rst eat up the space so we match the same+ let toOutfile title field =+ (filter (not . isSpace) (takeWhile (/= '(') title))+ ++ "-"+ ++ field - -- this produces results for all packages for all prefixes- -- [(packagenamewithversion, [Maybe Double])]- bmResults = map pkgGetResults pkginfo+ makeOneGraph infile field (title, prefixes) = do+ let title' =+ title+ ++ " (" ++ field ++ ")"+ ++ " (Lower is Better)"+ bgraph infile (toOutfile title field) field (cfg (title', prefixes)) -genGraphs :: CSV -> [(String, String)] -> IO ()-genGraphs csvData pkginfo = mapM_ (genOneGraph csvData pkginfo) bmGroups+ mapM_ (makeOneGraph input "time") charts+ mapM_ (makeOneGraph input "allocated") charts+ mapM_ (makeOneGraph input "maxrss") charts --- XXX display GHC version as well--- XXX display the OS/arch--- XXX fix the y axis labels--- XXX fix the legend position+-- Pass <input file> <comma separated list of packages> <True/False> main :: IO ()-main = do- args <- getArgs-- createDirectoryIfMissing True outputDir-- (out, _) <- readProcess_ "stack --system-ghc list-dependencies --bench"-- -- Get our streaming packages and their versions- let match [] = Nothing- match (_ : []) = Nothing- match (x : y : _) =- case elem x packages of- False -> Nothing- True -> Just (x, y)- pkginfo =- catMaybes- $ map match- $ map words (lines (T.unpack $ T.decodeUtf8 out))-- -- order them in the order specified in packages so that the order is- -- can be controlled by the user.- let pkginfo' = map (\x -> (x, fromJust $ lookup x pkginfo)) packages-- csvData <- parseCSVFromFile (head args)- case csvData of- Left e -> error $ show e- Right dat -> genGraphs dat pkginfo'- return ()+main = withCli createCharts
README.rst view
@@ -1,214 +1,442 @@ Streaming Benchmarks---------------------+==================== -Comprehensive, carefully crafted benchmarks for streaming operations and their-comparisons across notable Haskell streaming libraries including `streaming`,-`machines`, `pipes`, `conduit` and `streamly`. `Streamly-<https://github.com/composewell/streamly>`_ is a brand new streaming library-with beautiful high level and composable concurrency built-in, it is the-primary motivation for these benchmarks. We go to great lengths to make sure-that the benchmarks are correct, fair and reproducible. Please report if you-find something that is not right.+.. image:: https://badges.gitter.im/composewell/gitter.svg?+ :target: https://gitter.im/composewell/streamly+ :alt: Gitter chat -Benchmarks & Results---------------------+.. image:: https://img.shields.io/hackage/v/streaming-benchmarks.svg?style=flat+ :target: https://hackage.haskell.org/package/streaming-benchmarks+ :alt: Hackage -In all the benchmarks we work on a stream of a million consecutive numbers. We-start the sequence using a random number between 1 and 1000 and enumerate it to-make a total of a million elements using the streaming library's native-sequence enumeration API. Note that the efficiency of this sequence generation-may affect all performance numbers of the library because this is a constant-cost involved in all the benchmarks.+.. image:: https://travis-ci.org/composewell/streaming-benchmarks.svg?branch=master+ :target: https://travis-ci.org/composewell/streaming-benchmarks+ :alt: Unix Build Status -Note that, these benchmarks show results for conduit-1.3.0 which is a recently-released major version, it perhaps requires some work to get at par with the-earlier version i.e.-conduit-1.2.13.1 `which showed significantly better performance-<https://github.com/composewell/streaming-benchmarks/blob/269ac94fc59c76267b89b07690d9ea290096b95b/charts/AllOperationsataGlance.svg>`_-compared to the newer version.+.. image:: https://ci.appveyor.com/api/projects/status/8d1kgrrw9mmxv5xt?svg=true+ :target: https://ci.appveyor.com/project/harendra-kumar/streaming-benchmarks+ :alt: Windows Build status -When choosing a streaming library to use we should not be over obsessed about-the performance numbers as long as the performance is within reasonable bounds.-Whether the absolute performance or the differential among various libraries matters-or not may depend on your workload. If the cost of processing the data is-significantly higher then the streaming operations' overhead will just pale in-comparison and may not matter at all. Unless you are performing huge number of-tiny operations, performance difference may not be significant.+.. contents:: Table of Contents+ :depth: 1 -Composing Pipeline Stages-~~~~~~~~~~~~~~~~~~~~~~~~~+This package compares `streamly <https://github.com/composewell/streamly>`_, a+blazing fast streaming library providing native high level, declarative and+composable concurrency support, with popular streaming libraries e.g. vector,+streaming, pipes and conduit. This package has been motivated by `streamly+<https://github.com/composewell/streamly>`_, however, it is general purpose and+compares more libraries and benchmarks than shown here. Please send an email or+a pull request if the benchmarking code has a problem or is unfair to some+library in any way. -These benchmarks compare the performance when multiple operations are composed-serially in a pipeline. This is how the streaming libraries are supposed to be-used in real applications.+Benchmarks & Results+-------------------- -The `mapM` benchmark introduces four stages of `mapM` between the source and-the sink.+A stream of one million consecutive numbers is generated using monadic unfold+API ``unfoldrM``, these elements are then processed using a streaming+combinator under test (e.g. ``map``). The total time to process all one million+operations, and the maximum resident set size (rss) is measured and plotted for+each library. The underlying monad for each stream is the IO Monad. All the+libraries are compiled with GHC-8.4.3. All the benchmarks were run on an Apple+MacBook Pro computer with a single 2.2 GHz Intel Core i7 processor with 4 cores+and 16GB RAM. -`all-in-filters` composes four stages of a `filter` operation that passes all-the items through. Note that passing or blocking nature of the filter may-impact the results. Some libraries can do blocking more optimally by short-circuiting.+Highlights+~~~~~~~~~~ -`all-out-filters` composes four stages of a `filter` operation that `blocks`-all the items i.e. does not let anything pass through.+* ``streamly`` shows the best overall performance in terms of time as well as+ space. ``streamly`` and ``vector`` show similar performance except+ for the ``append`` operation where ``streamly`` is much better, and the+ ``filter`` operation where vector is faster.+* The ``append`` operation scales well only for ``streamly`` and ``conduit``.+ All other libraries show quadratic complexity on this operation.+* ``streaming`` performs slightly better than ``conduit`` when multiple+ operations are composed together even though in terms of individual+ operations it is slightly worse than ``conduit``.+* ``conduit`` and ``pipes`` show unusually large space utilization for+ ``take`` and ``drop`` operations (more than 100-150 MiB vs 3 MiB).+* ``drinkery`` shows very good performance too though not plotted here because+ of a small issue in measurement and lack of space.+* ``machines`` is roughly 2x slower than the slowest library here, and its+ maximum resident set size is close to 100 MiB for all operations (touching+ 300 MiB for ``take``) compared to the 3MiB for all other libraries. I am not+ sure if there is something wrong with the measurements or the benchmarking+ code, majority of the code is common to all libraries, any improvements in+ the machines benchmarking code are welcome. -The `map-with-all-in-filter` benchmark introduces four identical stages between-the source and the sink where each stage performs a simple `map` operation-followed by a `filter` operation that passes all the items through.+Key Operations+~~~~~~~~~~~~~~ -.. image:: charts/Composing Pipeline Stages.svg- :alt: Composing Pipeline Stages+The following diagram plots the time taken by key streaming operations to+process a million stream elements.+*Note: the time for streamly and vector is very low (600-700 microseconds) and+therefore can barely be seen in this graph.* -Individual Operations-~~~~~~~~~~~~~~~~~~~~~+.. |keyoperations-time| image:: charts-0/KeyOperations-time.svg+ :width: 75%+ :target: charts-0/KeyOperations-time.svg+ :alt: Time Cost of Key Streaming Operations -This chart shows microbenchmarks for all individual streaming operations for a-quick comparison. Operations are ordered more or less by increasing cost for-better visualization. If an operation is not present in a library then an empty-space is displayed instead of a colored bar in its slot. See the following-sections for details about what the benchmarks do.+|keyoperations-time| -.. image:: charts/All Operations at a Glance.svg- :alt: All Operations at a Glance+For those interested in the heap allocations, the following diagram+plots the overall heap allocations during each measurement period i.e. the+total allocations for processing one million stream elements. -Discarding and Folding-^^^^^^^^^^^^^^^^^^^^^^+.. |keyoperations-allocated| image:: charts-0/KeyOperations-allocated.svg+ :width: 75%+ :target: charts-0/KeyOperations-allocated.svg+ :alt: Heap allocations for Key Streaming Operations -This chart shows the cheapest of all operations, they include operations that-iterate over the stream and either discard all the elements or fold them to a-single value. They all do similar stuff and are generally expected to have-similar cost. Benchmarks include:+|keyoperations-allocated| -* `toNull:` Just discards all the elements in the stream.-* `drop-all`: drops ``n`` elements from the stream where ``n`` is set to the- length of the stream.-* `last`: drops all the elements except the last one.-* `fold`: adds all the elements in the stream to produces the sum.+The following diagram plots the maximum resident set size (rss) during the+measurement of each operation. In plain terms, it is the maximum amount of+physical memory that is utilized at any point during the measurement. -.. image:: charts/Discarding and Folding.svg- :alt: Discarding and Folding+.. |keyoperations-maxrss| image:: charts-0/KeyOperations-maxrss.svg+ :width: 75 %+ :target: charts-0/KeyOperations-maxrss.svg+ :alt: Maximum rss for Key Streaming Operations -Pure Transformation and Filtering-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^+|keyoperations-maxrss| -This is the next category which is a bit costlier than the previous one. Unlike-previous category these operations inspect the elements in the stream and-form a transformed stream based on a function on the value. Benchmarks include:++------------------------+----------------------------------------------------++| Benchmark | Description |++========================+====================================================++| drain | Just discards all the elements in the stream |++------------------------+----------------------------------------------------++| drop-all | drops all element using the ``drop`` operation |++------------------------+----------------------------------------------------++| last | extract the last element of the stream |++------------------------+----------------------------------------------------++| fold | sum all the numbers in the stream |++------------------------+----------------------------------------------------++| map | increments each number in the stream by 1 |++------------------------+----------------------------------------------------++| take-all | Use ``take`` to retain all the elements in the |+| | stream |++------------------------+----------------------------------------------------++| filter-even | Use ``filter`` to keep even numbers and discard |+| | odd numbers in the stream. |++------------------------+----------------------------------------------------++| scan | scans the stream using ``+`` operation |++------------------------+----------------------------------------------------++| mapM | transform the stream using a monadic action |++------------------------+----------------------------------------------------++| zip | combines corresponding elements of the two streams |+| | together |++------------------------+----------------------------------------------------+ -* `filter-all-out`: A filter that discards all the elements in the stream.-* `filter-all-in`: A filter that retains all the elements in the stream.-* `take-all`: take `n` elements from the stream where `n` is set to the length- of the stream. Effectively iterates through the stream and retains all of it.-* `takeWhile-true`: retains all elements of the stream using a condition that- always wvaluates to true.-* `map`: A pure transformation that increments each element by 1.-* `filter-even`: A filter that passes even elements in the stream i.e. half the- elements are kept and the other half discarded.-* `scan`: scans the stream using ``+`` operation.+Append Operation+~~~~~~~~~~~~~~~~ -.. image:: charts/Pure Transformation and Filtering.svg- :alt: Pure Transformation and Filtering+A million streams of single elements are created and appended together to+create a stream of million elements. The total time taken in this operation is+measured. *Note that vector, streaming and pipes show a quadratic+complexity (O(n^2)) on this benchmark and do not finish in a reasonable time*.+The time shown in the graph for these libraries is just+indicative, the actual time taken is much higher. -Monadic Transformation-^^^^^^^^^^^^^^^^^^^^^^+.. |append| image:: charts-0/AppendOperation-time.svg+ :width: 60 %+ :target: charts-0/AppendOperation-time.svg+ :alt: Cost of appending a million streams of single elements -This benchmark compares the monadic transformation of the stream using-``mapM``.+|append| -.. image:: charts/Monadic Transformation.svg- :alt: Monadic Transformation+toList Operation+~~~~~~~~~~~~~~~~ -Folding to List-^^^^^^^^^^^^^^^+A stream of a million elements is generated using ``unfoldrM`` and then+converted to a list. -This benchmark compares folding the stream to a list.+.. |toList| image:: charts-0/toListOperation-time.svg+ :width: 60 %+ :target: charts-0/toListOperation-time.svg+ :alt: Cost of converting a stream of million elements to a list -.. image:: charts/Folding to List.svg- :alt: Folding to List+|toList| -Zip and Concat-^^^^^^^^^^^^^^+Composing Multiple Operations+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Zip combines corresponding elements of the two streams together. Concat turns a-stream of containers into a stream of their elements.+A stream operation or a combination of stream operations are performed four+times in a row to measure how the composition scales for each library. A+million elements are passed through this composition. -.. image:: charts/Zipping and Concating Streams.svg- :alt: Zipping and Concating Streams+*Note: the time for streamly and vector is very low (600-700 microseconds) and+therefore can barely be seen in this graph.* -Studying the Scaling of Composition-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+.. |composed| image:: charts-0/ComposedOperations%3A4times-time.svg+ :width: 60 %+ :target: charts-0/ComposedOperations%3A4times-time.svg+ :alt: Cost when operations are composed -This category of benchmarks studies the effect of adding more stages in a-composition pipeline. For each library it displays the results when 1, 2, 3 or-4 pipeline stages are used. There are no graphs you can see the results in the-benchmark output.+|composed| ++------------------------+----------------------------------------------------++| Benchmark | Description |++========================+====================================================++| mapM | ``mapM`` four times in a row |++------------------------+----------------------------------------------------++| all-in-filters | four filters in a row, |+| | each allowing all elements in |++------------------------+----------------------------------------------------++| map-with-all-in-filter | ``map`` followed by ``filter`` composed four times |+| | serially |++------------------------+----------------------------------------------------++ How to Run ---------- +To quickly compare packages:+ :: - ./run.sh+ # Chart all the default packages+ ./run.sh --quick -After running you can find the charts generated in the ``charts`` directory. If-you are impatient use ``./run.sh --quick`` and you will get the results much-sooner though a tiny bit less precise. Note that quick mode won't generate the-graphs unless the latest ``gauge`` is used from github repo.+ # Compare a given list of packages+ # Available package names are: streamly, vector, streaming, pipes,+ # conduit, machines, drinkery, list, pure-vector+ ./run.sh --quick --select "streamly,vector" + # Show full results for the first packages and delta from that for+ # the rest of the packages.+ ./run.sh --quick --select "streamly,vector" --delta++After running you can find the charts generated in the ``charts`` directory.+If you have the patience to wait longer for the results remove the ``--quick``+option, the results are likely to be a tiny bit more accurate.++The ``list`` package above is the standard haskell lists in the base package,+and ``pure-vector`` is the vector package using pure API instead of the monadic+API.++Pedantic Mode+~~~~~~~~~~~~~+ Note that if different optimization flags are used on different packages, performance can sometimes badly suffer because of GHC inlining and-specialization not working optimally. If you want to be aboslutely sure that+specialization not working optimally. If you want to be absolutely sure that all packages and dependencies are compiled with the same optimization flags (``-O2``) use ``run.sh --pedantic``, it will install the stack snapshot in a private directory under the current directory and build them fresh with the ghc flags specified in ``stack-pedantic.yaml``. Be aware that this will require 1-2 GB extra disk space. -Important Points about Benchmarking Methodology------------------------------------------------+Adding New Libraries+~~~~~~~~~~~~~~~~~~~~ -``IO Monad:`` We run the benchmarks in the IO monad so that they are close to-real life usage. Note that most existing streaming benchmarks use pure code or-Identity monad which may produce entirely different results.+It is trivial to add a new package. This is how `a+benchmark file+<https://github.com/composewell/streaming-benchmarks/blob/master/Benchmarks/Streamly.hs>`_+for a streaming package looks like. Pull requests are welcome, I will be happy+to help, `just join the gitter chat+<https://github.com/composewell/streaming-benchmarks/blob/master/Benchmarks/Streamly.hs>`_+and ask! +Benchmarking Notes+------------------++Benchmarking is a tricky business. Though the benchmarks have been carefully+designed there may still be issues with the way benchmarking is being done or+the way they have been coded. If you find that something is being measured+unfairly or incorrectly please bring it to our notice by raising an issue or+sending an email or via gitter chat.++Measurement+~~~~~~~~~~~+ ``Benchmarking Tool:`` We use the `gauge-<https://github.com/vincenthz/hs-gauge>`_ package instead of criterion. We-spent a lot of time figuring out why benchmarking was not producing accurate-results. Criterion had several bugs due to which results were not reliable. We-fixed those bugs in ``gauge``. For example due to GC or CAF evaluation-interaction across benchmarks, the results of benchmarks running later in the-sequence were sometimes totally off the mark. We fixed that by running each-benchmark in a separate process in gauge. Another bug caused criterion to-report wrong mean.+<https://github.com/vincenthz/hs-gauge>`_ package for measurements instead of+criterion. There were several issues with criterion that we fixed in gauge to+get correct results. Each benchmark is run in a separate process to avoid any+interaction between benchmarks. -``Iterations:`` We pass a million elements through the streaming pipelines. We-do not rely on the benchmarking tool for this, it is explicitly done by the-benchmarking code and the benchmarking tool is asked to perform just one-iteration. We added fine grained control in `gauge-<https://github.com/vincenthz/hs-gauge>`_ to be able to do this.+Benchmarking Code+~~~~~~~~~~~~~~~~~ -``Effects of Optimizations:`` In some cases fusion or other optimizations can-just optimize out everything and produce ridiculously low results. To avoid-that we generate random numbers in the IO monad and pass those through the-pipeline rather than using some constant or predictable source.+* ``IO Monad:`` We run the benchmarks in the IO monad so that they are close to+ real life usage. Note that most existing streaming benchmarks use pure code+ or Identity monad which may produce entirely different results. -``GHC Optimization Flags:`` To make sure we are comparing fairly we make sure-that we compile the benchmarking code, the library code as well as all-dependencies using exactly the same GHC flags. GHC inlining and specialization-optimizations can make the code unpredictable if mixed flags are used. See the-``--pedantic`` option of the ``run.sh`` script.+* ``unfoldrM`` is used to generate the stream for two reasons, (1) it is+ monadic, (2) it reduces the generation overhead so that the actual streaming+ operation cost is amplified. If we use generation from a list there is a+ significant overhead in the generation itself because of the intermediate+ list structure. -``Benchmark Categories:`` We have two categories of benchmarks, one to measure-the performance of individual operations in isolation and the other to measure-the performance when multiple similar or different operations are composed-together in a pipeline.+* Unless we perform some real IO operation, the operation being benchmarked can+ get completely optimized out in some cases. We use a random number generation+ in the IO monad and feed it to the operation being benchmarked to avoid that+ issue. -Benchmarking Errors+GHC Inlining+------------++* ``Inlining:`` GHC simplifier is very fragile and inlining may affect the+ results in unpredictable ways unless you have spent enough time scrutinizing+ and optimizing everything carefully. Inlining is the biggest source of+ fragility in performance benchmarking. It can easily result in an order of+ magnitude drop in performance just because some operation is not correctly+ inlined. Note that this applies very well to the benchmarking code as well.++* ``GHC Optimization Flags:`` To make sure we are comparing fairly we make sure+ that we compile the benchmarking code, the library code as well as all+ dependencies using exactly the same GHC flags. GHC inlining and+ specialization optimizations can make the code unpredictable if mixed flags+ are used. See the ``--pedantic`` option of the ``run.sh`` script.++* ``Single file vs multiple files`` The best way to avoid issues is to have all+ the benchmarking code in a single file. However, in real life that is not the+ case and we also needed some modularity to scale the benchmarks to arbitrary+ number of libraries so we split it into per package file. As soon as the code+ was split into multiple files, performance of some libraries dropped, in some+ cases by 3-4x. Careful sprinkling of INLINE pragmas was required to bring it+ back to original. Even functions that seemed just 2 lines of code were not+ automatically inlined.++* When all the code was in a single file, not a single INLINE pragma was+ needed. But when split in multiple files even functions that were not+ exported from that file needed an INLINE pragma for equivalent performance.+ This is something that GHC may have to look at.++* The effect of inlining varied depending on the library. To make sure that we+ are using the fully optimized combination of inline or non-inline for each+ library we carefully studied the impact of inlining individual operations for+ each package. The current code is the best we could get for each package.++* There is something magical about streamly, not sure what it is. Even though+ all other libraries were impacted significantly for many ops, streamly seemed+ almost unaffected by splitting the benchmarking ops into a separate file! If+ we can find out why is it so, we could perhaps understand and use GHC+ inlining in a more predictable manner. Edit - CPS seems to be more immune to+ inlining, as soon as streamly started using direct style, it too became+ sensitive to inlining.++* This kind of unpredictable non-uniform impact of moving functions in+ different files shows that we are at the mercy of the GHC simplifier and+ always need to tune performance carefully after refactoring, to be sure that+ everything is fine. In other words, benchmarking and optimizing is crucial+ not just for the libraries `but for the users of the libraries as well`.++Streaming Libraries ------------------- -Benchmarking is a tricky business. Though the benchmarks have been carefully-designed there may still be issues with the way benchmarking is being done or-the way they have been coded. If you find that something is being measured-unfairly or incorrectly please bring it to our notice by raising an issue or-sending an email.+There are two dual paradigms for stream processing in Haskell. In the first+paradigm we represent a stream as a data type and use functions to work on it.+In the second paradigm we represent *stream processors* as data types and+provide them individual data elements to process, there is no explicit+representation of the stream as a data type. In the first paradigm we work with+data representation and in the second paradigm we work with function+representations. Both of these paradigms have equal expressive power. The+latter uses the monadic composition for data flow whereas the former does not+need monadic composition for straight line stream processing and therefore can+use it for higher level composition e.g. to compose streams in a product+style.++To see an example of the first paradigm, let us use the ``vector`` package to+represent a monadic stream of integers as ``Stream IO Int``. This data+representation of stream is passed explicitly to the stream processing+functions like ``filter`` and ``drop`` to manipulate it::++ import qualified Data.Vector.Fusion.Stream.Monadic as S++ stream :: S.Stream IO Int+ stream = S.fromList [1..100]++ main = do+ let str = (S.filter even . S.drop 10) stream+ toList str >>= putStrLn . show++Pure lists and vectors are the most basic examples of streams in this paradigm.+The streaming IO libraries just extend the same paradigm to monadic streaming.+The API of these libraries is very much similar to lists with a monad parameter+added.++The second paradigm is direct opposite of the first one, there is no stream+representation in this paradigm, instead we represent *stream processors* as+data types. A stream processor represents a particular process rather than+data, and we compose them together to create composite processors. We can call+them stream transducers or simply pipes. Using the ``machines`` package::++ import qualified Data.Machine as S++ producer :: S.SourceT IO Int+ producer = S.enumerateFromTo 1 100++ main = do+ let processor = producer S.~> S.dropping 10 S.~> S.filtered even+ S.runT processor >>= putStrLn . show++Both of these paradigms look almost the same, right? To see the difference+let's take a look at some types. In the first paradigm we have an explicit+stream type and the processing functions take the stream as input and produce+the transformed stream::++ stream :: S.Stream IO Int+ filter :: Monad m => (a -> Bool) -> Stream m a -> Stream m a++In the second paradigm, there is no stream data type, there are stream+processors, let's call them boxes that represent a process. We have a+*SourceT* box that represents a singled ended producer and a *Process* box or a+pipe that has two ends, an input end and an output end, a ``MachineT``+represents any kind of box. We put these boxes together using the ``~>``+operator and then run the resulting machine using ``runT``::++ producer :: S.SourceT IO Int+ filtered :: (a -> Bool) -> Process a a+ dropping :: Int -> Process a a+ (~>) :: Monad m => MachineT m k b -> ProcessT m b c -> MachineT m k c++Custom pipes can be created using a Monadic composition and primitives to+receive and send data usually called ``await`` and ``yield``.++.. |str| replace:: `streamly <https://github.com/composewell/streamly>`__+++-----------------------------------------------------------------------------++| Streaming libraries using the direct paradigm. |++------------------------+----------------------------------------------------++| Library | Remarks |++========================+====================================================++| vector | The simplest in this category, provides |+| | transformation and combining of monadic |+| | streams but no monadic composition of streams. |+| | Provides a very simple list like API. |++------------------------+----------------------------------------------------++| streaming | * Encodes a return value to be supplied when the |+| | stream ends. The monad instance passes on the |+| | streams and combines the return values. |+| | * Functor general |+| | * The API is more complicated than vector because |+| | of the return value and the functor layer. |++------------------------+----------------------------------------------------++| list-t | Provides straight line composition of streams |+| | as well as a list like monadic composition. |+| | The API is simple, just like ``vector``. |++------------------------+----------------------------------------------------++| | Like list-t, in addition to straight line |+| | composition it provides a list like monadic |+| | composition of streams, supports combining streams |+| | concurrently supports concurrent applicative and |+| | monadic composition. |+| |str| | The basic API is very much like lists and |+| | almost identical to ``vector`` streams. |++------------------------+----------------------------------------------------++++-----------------------------------------------------------------------------++| Streaming libraries using the pipes paradigm. |++------------------------+----------------------------------------------------++| Library | Remarks |++========================+====================================================++| conduit | ``await`` and ``yield`` data to upstream or |+| | downstream pipes; supports pushing leftovers back. |++------------------------+----------------------------------------------------++| pipes | ``await`` and ``yield`` data to upstream or |+| | downstream pipes |++------------------------+----------------------------------------------------++| machines | Can await from two sources, left and right. |++------------------------+----------------------------------------------------++
run.sh view
@@ -1,8 +1,12 @@ #!/bin/bash print_help () {- echo "Usage: $0 [--quick] [--pedantic] [--no-graph] [--no-measure] <benchmark-name or prefix> [min-samples]"- echo "Any arguments after a '--' will be passed as it is to guage"+ echo "Usage: $0 [--quick] [--select] [--delta] [--append] [--pedantic] [--no-graphs] [--no-measure] -- <gauge options>"+ echo+ echo "--select "streamly,vector" - would generate results only for those two libraries."+ echo "--delta - chart diff of subsequent packages from the first package"+ echo "Any arguments after a '--' are passed directly to guage"+ echo "You can omit '--' if the gauge args used do not start with a '-'." exit } @@ -12,13 +16,18 @@ exit 1 } +DELTA=False+ while test -n "$1" do case $1 in -h|--help|help) print_help ;; --quick) QUICK=1; shift ;;+ --select) shift; SELECTED=$1; shift ;;+ --delta) DELTA=True; shift ;;+ --append) APPEND=1; shift ;; --pedantic) PEDANTIC=1; shift ;;- --no-graph) GRAPH=0; shift ;;+ --no-graphs) GRAPH=0; shift ;; --no-measure) MEASURE=0; shift ;; --) shift; break ;; -*|--*) print_help ;;@@ -26,6 +35,13 @@ esac done +DEFAULT_PACKAGES="streamly,vector,streaming,conduit,pipes,machines,drinkery"++if test -z "$SELECTED"+then+ SELECTED=$DEFAULT_PACKAGES+fi+ STACK=stack if test "$PEDANTIC" = "1" then@@ -83,21 +99,32 @@ if test "$MEASURE" != "0" then- if test -e results.csv+ if test -e results.csv -a "$APPEND" != 1 then mv -f -v results.csv results.csv.prev fi - # We set min-samples to 1 so that we run with default benchmark duration of 5- # seconds, whatever number of samples are possible in that.- # We run just one iteration for each sample. Anyway the default is to run- # for 30 ms and most our benchmarks are close to that or more.+ MATCH_ARGS=""+ for i in $(echo $SELECTED | tr "," "\n")+ do+ MATCH_ARGS="$MATCH_ARGS -m pattern /$i"+ done++ # We set min-samples to 3 if we use less than three samples, statistical+ # analysis crashes. Note that the benchmark runs for a minimum of 5 seconds.+ # We use min-duration=0 to run just one iteration for each sample, we anyway+ # run a million ops in each iteration so we do not need more iterations.+ # However with fusion, million ops finish in microseconds. The+ # default is to run iterations worth minimum 30 ms and most of our benchmarks+ # are close to that or more.+ # --min-duration 0 \ $STACK bench --benchmark-arguments "$ENABLE_QUICK \ --include-first-iter \- --min-samples 1 \- --min-duration 0 \- --csv=results.csv \+ --min-samples 3 \+ --match exact \+ --csvraw=results.csv \ -v 2 \+ $MATCH_ARGS \ $BENCH_PROG $*" || die "Benchmarking failed" fi @@ -105,5 +132,5 @@ then echo echo "Generating charts from results.csv..."- $STACK exec makecharts results.csv+ $STACK exec makecharts results.csv $SELECTED $DELTA fi
+ stack-8.2.yaml view
@@ -0,0 +1,21 @@+resolver: lts-11.0+packages:+- '.'+extra-deps:+ - gauge-0.2.3+ - streamly-0.4.1+ - bench-graph-0.1.3++ # for lts-11.0+ - Chart-diagrams-1.8.3+ - SVGFonts-1.6.0.3+ - diagrams-core-1.4.0.1+ - diagrams-lib-1.4.2+ - diagrams-postscript-1.4+ - diagrams-svg-1.4.1.1+ - diagrams-solve-0.1.1+ - dual-tree-0.2.1+ - lens-4.15.4+ - free-4.12.4+ - drinkery-0.3+rebuild-ghc-options: true
stack-pedantic.yaml view
@@ -1,22 +1,16 @@-resolver: lts-11.0+resolver: lts-12.0 packages: - '.' extra-deps:- - gauge-0.2.1- - list-transformer-1.0.3- - streamly-0.1.1+ - streamly-0.4.1+ - drinkery-0.3 - # for lts-11.0- - Chart-diagrams-1.8.3+ # for lts-12.0+ - bench-graph-0.1.3+ - Chart-1.9+ - Chart-diagrams-1.9 - SVGFonts-1.6.0.3- - diagrams-core-1.4.0.1- - diagrams-lib-1.4.2- - diagrams-postscript-1.4- - diagrams-svg-1.4.1.1- - diagrams-solve-0.1.1- - dual-tree-0.2.1- - lens-4.15.4- - free-4.12.4 +rebuild-ghc-options: true ghc-options: "$everything": -O2
stack.yaml view
@@ -1,19 +1,14 @@-resolver: lts-11.0+resolver: lts-12.0 packages: - '.' extra-deps:- - gauge-0.2.1- - list-transformer-1.0.3- - streamly-0.1.1+ - streamly-0.4.1+ - drinkery-0.3 - # for lts-11.0- - Chart-diagrams-1.8.3+ # for lts-12.0+ - bench-graph-0.1.3+ - Chart-1.9+ - Chart-diagrams-1.9 - SVGFonts-1.6.0.3- - diagrams-core-1.4.0.1- - diagrams-lib-1.4.2- - diagrams-postscript-1.4- - diagrams-svg-1.4.1.1- - diagrams-solve-0.1.1- - dual-tree-0.2.1- - lens-4.15.4- - free-4.12.4++rebuild-ghc-options: true
streaming-benchmarks.cabal view
@@ -1,6 +1,6 @@ name: streaming-benchmarks category: Benchmark-version: 0.1.0+version: 0.2.0 license: MIT license-file: LICENSE author: Harendra Kumar@@ -11,31 +11,39 @@ copyright: Copyright (c) 2017 Harendra Kumar synopsis: Benchmarks to compare streaming packages description:- Comprehensive, carefully crafted benchmarks for streaming operations and- their comparisons across notable Haskell streaming libraries including- `streaming`, `machines`, `pipes`, `conduit` and `streamly`.- <http://hackage.haskell.org/package/streamly Streamly> is a new- streaming library with high level and composable concurrency built-in, it is- the primary motivation for these benchmarks. We have put a lot of effort to- make sure that the benchmarks are correct, fair and reproducible. Please- report if you find something that is not right.+ Benchmarks along with with pretty comparative graph generation for streaming+ operations and their comparisons across notable Haskell streaming libraries+ including `streamly`, `vector`, `streaming`, `machines`, `pipes`, and+ `conduit`.+ <http://hackage.haskell.org/package/streamly streamly> is a streaming library+ with native - high level, declarative and composable concurrency, it+ is the primary motivation for these benchmarks. .- If you are using @stack@ then use @./run.sh@ to run the benchmarks;- charts will be generated in the `charts` directory.+ If you are using @stack@ then you can just use @./run.sh@ to run the+ benchmarks; use @--quick@ option to get the result quickly; charts will be+ generated in the `charts` directory. Use @./run.sh --help@ for all script+ options. .- With any build tool, run the benchmarks with- @--csv=results.csv@ as arguments and then use @makecharts results.csv@ to- create the charts. In case you want to be pedantic about accurate results- then you can run the benchmarks in the same way as @run.sh@ invokes them.+ With any build tool, run the benchmarks with @--csv=results.csv@ as arguments+ (you can pass any @gauge@ arguments including @--quick@) and then use+ @makecharts results.csv "streamly,vector,..." False@ to create the charts.+ The second argument to @makecharts@ is the list of package names, the third+ argument is whether to plot full or diff from the first package.+ .+ See the README file shipped with the package or+ <https://github.com/composewell/streaming-benchmarks in the github repo>+ for more details. The github repo also shows the latest comparative graphs. cabal-version: >= 1.10-tested-with: GHC==8.2.2+tested-with: GHC==8.2.2, GHC==8.4.3 build-type: Simple extra-source-files:+ Changelog.md run.sh README.rst licenses/Readme.txt licenses/LICENSE.machines+ stack-8.2.yaml stack.yaml stack-pedantic.yaml @@ -48,6 +56,20 @@ type: exitcode-stdio-1.0 hs-source-dirs: . main-is: Benchmarks.hs+ other-modules: Benchmarks.Common+ , Benchmarks.BenchmarkTH+ , Benchmarks.Streamly+ , Benchmarks.Vector+ , Benchmarks.Streaming+ -- , Benchmarks.LogicT+ -- , Benchmarks.ListT+ -- , Benchmarks.ListTransformer+ , Benchmarks.Conduit+ , Benchmarks.Pipes+ , Benchmarks.Machines+ , Benchmarks.Drinkery+ , Benchmarks.List+ , Benchmarks.VectorPure ghc-options: -O2 -Wall -with-rtsopts "-T" if impl(ghc >= 8.0) ghc-options: -Wcompat@@ -62,22 +84,24 @@ build-depends: base == 4.*, deepseq >= 1.4.0 && < 1.5,- gauge >= 0.2.1 && < 0.3,+ gauge >= 0.2.3 && < 0.3, mtl >= 2 && < 2.3, random >= 1.0 && < 2.0, transformers >= 0.4 && < 0.6,+ template-haskell >= 2.10 && < 2.14, - conduit >= 1.3 && < 1.4,- list-transformer >= 1.0.2 && < 1.1,- list-t >= 0.4.6 && < 1.1,- logict >= 0.5.0 && < 0.7,+ vector >= 0.12 && < 0.13,+ streamly >= 0.2.1 && < 0.5,+ streaming >= 0.1.4 && < 0.3, machines >= 0.6.0 && < 0.7, pipes >= 4 && < 4.4,+ conduit >= 1.3 && < 1.4,+ drinkery >= 0.3 && < 0.4 -- does not build with lts-11.0 -- simple-conduit >= 0.4.0 && < 0.7,- streaming >= 0.1.4 && < 0.3,- vector >= 0.12 && < 0.13,- streamly >= 0.1.1 && < 0.2+ -- list-transformer >= 1.0.2 && < 1.1,+ -- list-t >= 0.4.6 && < 1.1,+ -- logict >= 0.5.0 && < 0.7, executable makecharts default-language: Haskell2010@@ -88,11 +112,14 @@ build-depends: base == 4.*+ , bench-graph >= 0.1 && < 0.2 , bytestring >= 0.9 && < 0.11- , Chart >= 1.6 && < 1.9- , Chart-diagrams >= 1.6 && < 1.9+ , Chart >= 1.6 && < 2+ , Chart-diagrams >= 1.6 && < 2 , csv >= 0.1 && < 0.2 , directory >= 1.2 && < 1.4 , split >= 0.2 && < 0.3 , text >= 1.1.1 && < 1.3+ , transformers >= 0.4 && < 0.6 , typed-process >= 0.1.0.0 && < 0.3+ , getopt-generics >= 0.11 && < 0.14