on master help sign in
Add concat/append benchmark for concurrent streams

Measure space usage in interesting cases. The motivation for these benchmarks is parallel stream consuming a lot of stack space in concatMap case. These benchmarks will hopefully catch any such problems in future. These benchmarks may take a lot of time to allow memory consumption to slowly buildup to noticeable amount in case there is a problem. Therefore they are enabled only in dev builds. We can use `--include-first-iter`, `--min-duration 0` options in gauge to run them in reasonable time. They need to be run with `--measure-with` option to allow isolated runs, otherwise memory measurement won't be correct.

commitd6eb1381655a4a1d6faef3d2eb113dd82b5f74bd authorHarendra Kumar <harendra.kumar@gmail.com> dateFri 28 Jun 2019, 02:54 (7 years ago) parented7f4ab
2 files changed+122
benchmark/Concurrent.hs+89 streamly.cabal+33
benchmark/Concurrent.hsnew file+89
@@ -0,0 +1,89 @@
1+{-# LANGUAGE RankNTypes #-}
2+-- |
3+-- Module : Main
4+-- Copyright : (c) 2018 Harendra Kumar
5+--
6+-- License : BSD3
7+-- Maintainer : harendra.kumar@gmail.com
8+
9+import Control.Concurrent
10+import Control.Monad (when)
11+-- import Data.IORef
12+
13+import Gauge
14+import Streamly
15+import qualified Streamly.Prelude as S
16+
17+-------------------------------------------------------------------------------
18+-- Append
19+-------------------------------------------------------------------------------
20+
21+-- Single work item yielded per thread
22+{-# INLINE append #-}
23+append :: IsStream t
24+ => Int -> Int -> Int -> (t IO Int -> SerialT IO Int) -> IO ()
25+append buflen tcount d t =
26+ let work = (\i -> when (d /= 0) (threadDelay d) >> return i)
27+ in S.drain
28+ $ t
29+ $ maxBuffer buflen
30+ $ maxThreads (-1)
31+ $ S.fromFoldableM $ map work [1..tcount]
32+
33+-- Big stream of items yielded per thread
34+{-# INLINE concated #-}
35+concated :: Int -> Int -> Int -> Int -> (forall a. SerialT IO a -> SerialT IO a -> SerialT IO a) -> IO ()
36+concated buflen threads d elems t =
37+ let work = (\i -> (S.replicateM i ((when (d /= 0) (threadDelay d)) >> return i)))
38+ in S.drain
39+ $ adapt
40+ $ maxThreads (-1)
41+ $ maxBuffer buflen
42+ $ S.concatMapBy t work
43+ $ S.replicate threads elems
44+
45+appendGroup :: Int -> Int -> Int -> [Benchmark]
46+appendGroup buflen threads delay =
47+ [ -- bench "serial" $ nfIO $ append buflen threads delay serially
48+ bench "ahead" $ nfIO $ append buflen threads delay aheadly
49+ , bench "async" $ nfIO $ append buflen threads delay asyncly
50+ , bench "wAsync" $ nfIO $ append buflen threads delay wAsyncly
51+ , bench "parallel" $ nfIO $ append buflen threads delay parallely
52+ ]
53+
54+concatGroup :: Int -> Int -> Int -> Int -> [Benchmark]
55+concatGroup buflen threads delay n =
56+ [ bench "serial" $ nfIO $ concated buflen threads delay n serial
57+ , bench "ahead" $ nfIO $ concated buflen threads delay n ahead
58+ , bench "async" $ nfIO $ concated buflen threads delay n async
59+ , bench "wAsync" $ nfIO $ concated buflen threads delay n wAsync
60+ , bench "parallel" $ nfIO $ concated buflen threads delay n parallel
61+ ]
62+
63+main :: IO ()
64+main = do
65+ defaultMain
66+ [ -- bgroup "append/buf-1-threads-10k-0sec" (appendGroup 1 10000 0)
67+ -- , bgroup "append/buf-100-threads-100k-0sec" (appendGroup 100 100000 0)
68+ bgroup "append/buf-10k-threads-10k-5sec" (appendGroup 10000 10000 5000000)
69+ -- bgroup "concat/buf-1-threads-100k-count-1" (concatGroup 1 100000 0 1)
70+ -- bgroup "concat/buf-1-threads-1-count-10m" (concatGroup 1 1 0 10000000)
71+ , bgroup "concat/buf-100-threads-100-count-500k" (concatGroup 100 100 0 500000)
72+ {-
73+ , bgroup "forkIO-5000ms-10k" $
74+ let delay = threadDelay 5000000
75+ count = 10000 :: Int
76+ list = [1..count]
77+ work i = delay >> return i
78+ in
79+ [ bench "discard" $ nfIO $ do
80+ mapM_ (\i -> forkIO $ work i >> return ()) list
81+ threadDelay 6000000
82+ , bench "collect" $ nfIO $ do
83+ ref <- newIORef []
84+ mapM_ (\i -> forkIO $ work i >>=
85+ \j -> atomicModifyIORef ref $ \xs -> (j : xs, ())) list
86+ threadDelay 6000000
87+ ]
88+ -}
89+ ]
streamly.cabal+33
@@ -710,6 +710,39 @@ benchmark fileio
710710 , typed-process >= 0.2.3 && < 0.3
711711 , deepseq >= 1.4.1 && < 1.5
712712
713+benchmark concurrent
714+ type: exitcode-stdio-1.0
715+ hs-source-dirs: benchmark
716+ main-is: Concurrent.hs
717+ default-language: Haskell2010
718+ ghc-options: -O2 -Wall -fspec-constr-recursive=10 -with-rtsopts "-T"
719+
720+ if flag(has-llvm)
721+ ghc-options: -fllvm
722+
723+ if flag(dev)
724+ cpp-options: -DDEVBUILD
725+ ghc-options: -Wmissed-specialisations
726+ -Wall-missed-specialisations
727+ -fno-ignore-asserts
728+ if impl(ghc >= 8.0)
729+ ghc-options: -Wcompat
730+ -Wunrecognised-warning-flags
731+ -Widentities
732+ -Wincomplete-record-updates
733+ -Wincomplete-uni-patterns
734+ -Wredundant-constraints
735+ -Wnoncanonical-monad-instances
736+ -Wnoncanonical-monadfail-instances
737+ if flag(dev)
738+ buildable: True
739+ build-depends:
740+ streamly
741+ , base >= 4.8 && < 5
742+ , gauge >= 0.2.4 && < 0.3
743+ else
744+ buildable: False
745+
713746-------------------------------------------------------------------------------
714747-- Internal benchmarks for unexposed modules
715748-------------------------------------------------------------------------------