packages feed

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 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