2{-# LANGUAGE ConstraintKinds #-}
3{-# LANGUAGE DeriveTraversable #-}
4{-# LANGUAGE FlexibleContexts #-}
5{-# LANGUAGE FlexibleInstances #-}
6{-# LANGUAGE GeneralizedNewtypeDeriving#-}
7{-# LANGUAGE InstanceSigs #-}
8{-# LANGUAGE MultiParamTypeClasses #-}
9{-# LANGUAGE UndecidableInstances #-} -- XXX
12-- Module : Streamly.Streams.Parallel
13-- Copyright : (c) 2017 Harendra Kumar
16-- Maintainer : harendra.kumar@gmail.com
17-- Stability : experimental
21module Streamly.Streams.Parallel
28 -- * Function application
37import Control.Monad (ap)
38import Control.Monad.Base (MonadBase(..), liftBaseDefault)
39import Control.Monad.Catch (MonadThrow, throwM)
40-- import Control.Monad.Error.Class (MonadError(..))
41import Control.Monad.IO.Class (MonadIO(..))
42import Control.Monad.Reader.Class (MonadReader(..))
43import Control.Monad.State.Class (MonadState(..))
44import Control.Monad.Trans.Class (MonadTrans(lift))
45import Data.Functor (void)
46import Data.Maybe (fromJust)
47import Data.Semigroup (Semigroup(..))
48import Prelude hiding (map)
50import Streamly.Streams.SVar (fromSVar)
51import Streamly.Streams.Serial (map)
53import Streamly.Streams.StreamK (IsStream(..), Stream(..), adapt)
54import qualified Streamly.Streams.StreamK as K
56#include "Instances.hs"
58-------------------------------------------------------------------------------
60-------------------------------------------------------------------------------
62{-# NOINLINE runOne #-}
65 => State Stream m a -> Stream m a -> Maybe WorkerInfo -> m ()
66runOne st m winfo = unStream m st stop single yieldk
70 sv = fromJust $ streamVar st
71 mrun = runInIO $ svarMrun sv
73 withLimitCheck action = do
74 yieldLimitOk <- liftIO $ decrementYieldLimitPost sv
77 else liftIO $ cleanupSVarFromWorker sv
79 stop = liftIO $ sendStop sv winfo
80 sendit a = liftIO $ sendYield sv winfo (ChildYield a)
81 single a = sendit a >> withLimitCheck stop
83 -- XXX there is no flow control in parallel case. We should perhaps use a
84 -- queue and queue it back on that and exit the thread when the outputQueue
85 -- overflows. Parallel is dangerous because it can accumulate unbounded
86 -- output in the buffer.
87 yieldk a r = void (sendit a)
88 >> withLimitCheck (void $ liftIO $ mrun $ runOne st r winfo)
90{-# NOINLINE forkSVarPar #-}
91forkSVarPar :: MonadAsync m => Stream m a -> Stream m a -> Stream m a
92forkSVarPar m r = Stream $ \st stp sng yld -> do
93 sv <- newParallelVar st
94 pushWorkerPar sv (runOne st{streamVar = Just sv} m)
95 pushWorkerPar sv (runOne st{streamVar = Just sv} r)
96 unStream (fromSVar sv) (rstState st) stp sng yld
98{-# INLINE joinStreamVarPar #-}
99joinStreamVarPar :: MonadAsync m
100 => SVarStyle -> Stream m a -> Stream m a -> Stream m a
101joinStreamVarPar style m1 m2 = Stream $ \st stp sng yld ->
103 Just sv | svarStyle sv == style -> do
104 pushWorkerPar sv (runOne st m1)
105 unStream m2 st stp sng yld
106 _ -> unStream (forkSVarPar m1 m2) st stp sng yld
108{-# INLINE parallelStream #-}
109parallelStream :: MonadAsync m => Stream m a -> Stream m a -> Stream m a
110parallelStream = joinStreamVarPar ParallelVar
112-- | XXX we can implement it more efficienty by directly implementing instead
113-- of combining streams using parallel.
114{-# INLINE consMParallel #-}
115consMParallel :: MonadAsync m => m a -> Stream m a -> Stream m a
116consMParallel m r = K.yieldM m `parallelStream` r
118-- | Polymorphic version of the 'Semigroup' operation '<>' of 'ParallelT'
119-- Merges two streams concurrently.
122{-# INLINE parallel #-}
123parallel :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a
124parallel m1 m2 = fromStream $ Stream $ \st stp sng yld ->
125 unStream (parallelStream (toStream m1) (toStream m2))
128------------------------------------------------------------------------------
129-- Convert a stream to parallel
130------------------------------------------------------------------------------
132mkParallel :: (IsStream t, MonadAsync m) => t m a -> m (t m a)
134 sv <- newParallelVar defState
135 pushWorkerPar sv (runOne defState{streamVar = Just sv} $ toStream m)
138------------------------------------------------------------------------------
139-- Stream to stream concurrent function application
140------------------------------------------------------------------------------
142{-# INLINE applyWith #-}
143applyWith :: (IsStream t, MonadAsync m) => (t m a -> t m b) -> t m a -> t m b
144applyWith f m = fromStream $ Stream $ \st stp sng yld -> do
145 sv <- newParallelVar (rstState st)
146 pushWorkerPar sv (runOne st{streamVar = Just sv} (toStream m))
147 unStream (toStream $ f $ fromSVar sv) (rstState st) stp sng yld
149------------------------------------------------------------------------------
150-- Stream runner concurrent function application
151------------------------------------------------------------------------------
153{-# INLINE runWith #-}
154runWith :: (IsStream t, MonadAsync m) => (t m a -> m b) -> t m a -> m b
156 sv <- newParallelVar defState
157 pushWorkerPar sv (runOne defState{streamVar = Just sv} $ toStream m)
160------------------------------------------------------------------------------
161-- Concurrent Application
162------------------------------------------------------------------------------
170-- | Parallel function application operator for streams; just like the regular
171-- function application operator '$' except that it is concurrent. The
172-- following code prints a value every second even though each stage adds a 1
178-- S.mapM (\\x -> threadDelay 1000000 >> print x)
179-- |$ S.repeatM (threadDelay 1000000 >> return 1)
186(|$) :: (IsStream t, MonadAsync m) => (t m a -> t m b) -> t m a -> t m b
187f |$ x = applyWith f x
189-- | Parallel reverse function application operator for streams; just like the
190-- regular reverse function application operator '&' except that it is
195-- S.repeatM (threadDelay 1000000 >> return 1)
196-- |& S.mapM (\\x -> threadDelay 1000000 >> print x)
203(|&) :: (IsStream t, MonadAsync m) => t m a -> (t m a -> t m b) -> t m b
206-- | Parallel function application operator; applies a @run@ or @fold@ function
207-- to a stream such that the fold consumer and the stream producer run in
208-- parallel. A @run@ or @fold@ function reduces the stream to a value in the
209-- underlying monad. The @.@ at the end of the operator is a mnemonic for
210-- termination of the stream.
213-- S.foldlM' (\\_ a -> threadDelay 1000000 >> print a) ()
214-- |$. S.repeatM (threadDelay 1000000 >> return 1)
221(|$.) :: (IsStream t, MonadAsync m) => (t m a -> m b) -> t m a -> m b
224-- | Parallel reverse function application operator for applying a run or fold
225-- functions to a stream. Just like '|$.' except that the operands are reversed.
228-- S.repeatM (threadDelay 1000000 >> return 1)
229-- |&. S.foldlM' (\\_ a -> threadDelay 1000000 >> print a) ()
236(|&.) :: (IsStream t, MonadAsync m) => t m a -> (t m a -> m b) -> m b
239------------------------------------------------------------------------------
241------------------------------------------------------------------------------
243-- | Async composition with simultaneous traversal of all streams.
245-- The Semigroup instance of 'ParallelT' concurrently /merges/ two streams,
246-- running both strictly concurrently and yielding elements from both streams
247-- as they arrive. When multiple streams are combined using 'ParallelT' each
248-- one is evaluated in its own thread and the results produced are presented in
249-- the combined stream on a first come first serve basis.
251-- 'AsyncT' and 'WAsyncT' are /concurrent lookahead streams/ each with a
252-- specific type of consumption pattern (depth first or breadth first). Since
253-- they are lookahead, they may introduce certain default latency in starting
254-- more concurrent tasks for efficiency reasons or may put a default limitation
255-- on the resource consumption (e.g. number of concurrent threads for
256-- lookahead). If we look at the implementation detail, they both can share a
257-- pool of worker threads to evaluate the streams in the desired pattern and at
258-- the desired rate. However, 'ParallelT' uses a separate runtime thread to
259-- evaluate each stream.
261-- 'WAsyncT' is similar to 'ParallelT', as both of them evaluate the
262-- constituent streams fairly in a round robin fashion.
263-- However, the key difference is that 'WAsyncT' is lazy or pull driven
264-- whereas 'ParallelT' is strict or push driven. 'ParallelT' immediately
265-- starts concurrent evaluation of both the streams (in separate threads) and
266-- later picks the results whereas 'WAsyncT' may wait for a certain latency
267-- threshold before initiating concurrent evaluation of the next stream. The
268-- concurrent scheduling of the next stream or the degree of concurrency is
269-- driven by the feedback from the consumer. In case of 'ParallelT' each stream
270-- is evaluated in a separate thread and results are /pushed/ to a shared
271-- output buffer, the evaluation rate is controlled by blocking when the buffer
274-- Concurrent lookahead streams are generally more efficient than
275-- 'ParallelT' and can work pretty efficiently even for smaller tasks because
276-- they do not necessarily use a separate thread for each task. So they should
277-- be preferred over 'ParallelT' especially when efficiency is a concern and
278-- simultaneous strict evaluation is not a requirement. 'ParallelT' is useful
279-- for cases when the streams are required to be evaluated simultaneously
280-- irrespective of how the consumer consumes them e.g. when we want to race
281-- two tasks and want to start both strictly at the same time or if we have
282-- timers in the parallel tasks and our results depend on the timers being
283-- started at the same time. We can say that 'ParallelT' is almost the same
284-- (modulo some implementation differences) as 'WAsyncT' when the latter is
285-- used with unlimited lookahead and zero latency in initiating lookahead.
288-- main = ('toList' . 'parallely' $ (fromFoldable [1,2]) \<> (fromFoldable [3,4])) >>= print
294-- When streams with more than one element are merged, it yields whichever
295-- stream yields first without any bias, unlike the 'Async' style streams.
297-- Any exceptions generated by a constituent stream are propagated to the
298-- output stream. The output and exceptions from a single stream are guaranteed
299-- to arrive in the same order in the resulting stream as they were generated
300-- in the input stream. However, the relative ordering of elements from
301-- different streams in the resulting stream can vary depending on scheduling
302-- and generation delays.
304-- Similarly, the 'Monad' instance of 'ParallelT' runs /all/ iterations
305-- of the loop concurrently.
309-- import qualified "Streamly.Prelude" as S
310-- import Control.Concurrent
312-- main = 'runStream' . 'parallely' $ do
313-- n <- return 3 \<\> return 2 \<\> return 1
315-- threadDelay (n * 1000000)
316-- myThreadId >>= \\tid -> putStrLn (show tid ++ ": Delay " ++ show n)
319-- ThreadId 40: Delay 1
320-- ThreadId 39: Delay 2
321-- ThreadId 38: Delay 3
324-- Note that parallel composition can only combine a finite number of
325-- streams as it needs to retain state for each unfinished stream.
328newtype ParallelT m a = ParallelT {getParallelT :: Stream m a}
329 deriving (MonadTrans, Foldable, Traversable)
331-- | A parallely composing IO stream of elements of type @a@.
332-- See 'ParallelT' documentation for more details.
335type Parallel = ParallelT IO
337-- | Fix the type of a polymorphic stream as 'ParallelT'.
340parallely :: IsStream t => ParallelT m a -> t m a
343instance IsStream ParallelT where
344 toStream = getParallelT
345 fromStream = ParallelT
348 {-# SPECIALIZE consM :: IO a -> ParallelT IO a -> ParallelT IO a #-}
349 consM m r = fromStream $ consMParallel m (toStream r)
352 {-# SPECIALIZE (|:) :: IO a -> ParallelT IO a -> ParallelT IO a #-}
355------------------------------------------------------------------------------
357------------------------------------------------------------------------------
359instance MonadAsync m => Semigroup (ParallelT m a) where
362------------------------------------------------------------------------------
364------------------------------------------------------------------------------
366instance MonadAsync m => Monoid (ParallelT m a) where
370------------------------------------------------------------------------------
372------------------------------------------------------------------------------
374instance MonadAsync m => Monad (ParallelT m) where
377 = ParallelT $ K.bindWith parallelStream m (getParallelT . f)
379------------------------------------------------------------------------------
381------------------------------------------------------------------------------
383MONAD_APPLICATIVE_INSTANCE(ParallelT,MONADPARALLEL)
384MONAD_COMMON_INSTANCES(ParallelT, MONADPARALLEL)
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list