2{-# LANGUAGE ConstraintKinds #-}
3{-# LANGUAGE FlexibleContexts #-}
4{-# LANGUAGE FlexibleInstances #-}
5{-# LANGUAGE GeneralizedNewtypeDeriving#-}
6{-# LANGUAGE InstanceSigs #-}
7{-# LANGUAGE MultiParamTypeClasses #-}
8{-# LANGUAGE StandaloneDeriving #-}
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 #-}
63runOne :: MonadIO m => State Stream m a -> Stream m a -> WorkerInfo -> m ()
64runOne st m winfo = unStream m st stop single yieldk
68 sv = fromJust $ streamVar st
70 withLimitCheck action = do
71 yieldLimitOk <- liftIO $ decrementYieldLimitPost sv
74 else liftIO $ cleanupSVarFromWorker sv
76 stop = liftIO $ sendStop sv winfo
77 sendit a = liftIO $ sendYield sv winfo (ChildYield a)
78 single a = sendit a >> withLimitCheck stop
80 -- XXX there is no flow control in parallel case. We should perhaps use a
81 -- queue and queue it back on that and exit the thread when the outputQueue
82 -- overflows. Parallel is dangerous because it can accumulate unbounded
83 -- output in the buffer.
84 yieldk a r = void (sendit a) >> withLimitCheck (runOne st r winfo)
86{-# NOINLINE forkSVarPar #-}
87forkSVarPar :: MonadAsync m => Stream m a -> Stream m a -> Stream m a
88forkSVarPar m r = Stream $ \st stp sng yld -> do
89 sv <- newParallelVar st
90 pushWorkerPar sv (runOne st{streamVar = Just sv} m)
91 pushWorkerPar sv (runOne st{streamVar = Just sv} r)
92 (unStream (fromSVar sv)) (rstState st) stp sng yld
94{-# INLINE joinStreamVarPar #-}
95joinStreamVarPar :: MonadAsync m
96 => SVarStyle -> Stream m a -> Stream m a -> Stream m a
97joinStreamVarPar style m1 m2 = Stream $ \st stp sng yld ->
99 Just sv | svarStyle sv == style -> do
100 pushWorkerPar sv (runOne st m1)
101 unStream m2 st stp sng yld
102 _ -> unStream (forkSVarPar m1 m2) st stp sng yld
104{-# INLINE parallelStream #-}
105parallelStream :: MonadAsync m => Stream m a -> Stream m a -> Stream m a
106parallelStream = joinStreamVarPar ParallelVar
108-- | XXX we can implement it more efficienty by directly implementing instead
109-- of combining streams using parallel.
110{-# INLINE consMParallel #-}
111consMParallel :: MonadAsync m => m a -> Stream m a -> Stream m a
112consMParallel m r = K.yieldM m `parallelStream` r
114-- | Polymorphic version of the 'Semigroup' operation '<>' of 'ParallelT'
115-- Merges two streams concurrently.
118{-# INLINE parallel #-}
119parallel :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a
120parallel m1 m2 = fromStream $ Stream $ \st stp sng yld -> do
121 unStream (parallelStream (toStream m1) (toStream m2))
124------------------------------------------------------------------------------
125-- Convert a stream to parallel
126------------------------------------------------------------------------------
128mkParallel :: (IsStream t, MonadAsync m) => t m a -> m (t m a)
130 sv <- newParallelVar defState
131 pushWorkerPar sv (runOne defState{streamVar = Just sv} $ toStream m)
134------------------------------------------------------------------------------
135-- Stream to stream concurrent function application
136------------------------------------------------------------------------------
138{-# INLINE applyWith #-}
139applyWith :: (IsStream t, MonadAsync m) => (t m a -> t m b) -> t m a -> t m b
140applyWith f m = fromStream $ Stream $ \st stp sng yld -> do
141 sv <- newParallelVar (rstState st)
142 pushWorkerPar sv (runOne st{streamVar = Just sv} (toStream m))
143 unStream (toStream $ f $ fromSVar sv) (rstState st) stp sng yld
145------------------------------------------------------------------------------
146-- Stream runner concurrent function application
147------------------------------------------------------------------------------
149{-# INLINE runWith #-}
150runWith :: (IsStream t, MonadAsync m) => (t m a -> m b) -> t m a -> m b
152 sv <- newParallelVar defState
153 pushWorkerPar sv (runOne defState{streamVar = Just sv} $ toStream m)
156------------------------------------------------------------------------------
157-- Concurrent Application
158------------------------------------------------------------------------------
166-- | Parallel function application operator for streams; just like the regular
167-- function application operator '$' except that it is concurrent. The
168-- following code prints a value every second even though each stage adds a 1
174-- S.mapM (\\x -> threadDelay 1000000 >> print x)
175-- |$ S.repeatM (threadDelay 1000000 >> return 1)
182(|$) :: (IsStream t, MonadAsync m) => (t m a -> t m b) -> t m a -> t m b
183f |$ x = applyWith f x
185-- | Parallel reverse function application operator for streams; just like the
186-- regular reverse function application operator '&' except that it is
191-- S.repeatM (threadDelay 1000000 >> return 1)
192-- |& S.mapM (\\x -> threadDelay 1000000 >> print x)
199(|&) :: (IsStream t, MonadAsync m) => t m a -> (t m a -> t m b) -> t m b
202-- | Parallel function application operator; applies a @run@ or @fold@ function
203-- to a stream such that the fold consumer and the stream producer run in
204-- parallel. A @run@ or @fold@ function reduces the stream to a value in the
205-- underlying monad. The @.@ at the end of the operator is a mnemonic for
206-- termination of the stream.
209-- S.foldlM' (\\_ a -> threadDelay 1000000 >> print a) ()
210-- |$. S.repeatM (threadDelay 1000000 >> return 1)
217(|$.) :: (IsStream t, MonadAsync m) => (t m a -> m b) -> t m a -> m b
220-- | Parallel reverse function application operator for applying a run or fold
221-- functions to a stream. Just like '|$.' except that the operands are reversed.
224-- S.repeatM (threadDelay 1000000 >> return 1)
225-- |&. S.foldlM' (\\_ a -> threadDelay 1000000 >> print a) ()
232(|&.) :: (IsStream t, MonadAsync m) => t m a -> (t m a -> m b) -> m b
235------------------------------------------------------------------------------
237------------------------------------------------------------------------------
239-- | Async composition with simultaneous traversal of all streams.
241-- The Semigroup instance of 'ParallelT' concurrently /merges/ two streams,
242-- running both strictly concurrently and yielding elements from both streams
243-- as they arrive. When multiple streams are combined using 'ParallelT' each
244-- one is evaluated in its own thread and the results produced are presented in
245-- the combined stream on a first come first serve basis.
247-- 'AsyncT' and 'WAsyncT' are /concurrent lookahead streams/ each with a
248-- specific type of consumption pattern (depth first or breadth first). Since
249-- they are lookahead, they may introduce certain default latency in starting
250-- more concurrent tasks for efficiency reasons or may put a default limitation
251-- on the resource consumption (e.g. number of concurrent threads for
252-- lookahead). If we look at the implementation detail, they both can share a
253-- pool of worker threads to evaluate the streams in the desired pattern and at
254-- the desired rate. However, 'ParallelT' uses a separate runtime thread to
255-- evaluate each stream.
257-- 'WAsyncT' is similar to 'ParallelT', as both of them evaluate the
258-- constituent streams fairly in a round robin fashion.
259-- However, the key difference is that 'WAsyncT' is lazy or pull driven
260-- whereas 'ParallelT' is strict or push driven. 'ParallelT' immediately
261-- starts concurrent evaluation of both the streams (in separate threads) and
262-- later picks the results whereas 'WAsyncT' may wait for a certain latency
263-- threshold before initiating concurrent evaluation of the next stream. The
264-- concurrent scheduling of the next stream or the degree of concurrency is
265-- driven by the feedback from the consumer. In case of 'ParallelT' each stream
266-- is evaluated in a separate thread and results are /pushed/ to a shared
267-- output buffer, the evaluation rate is controlled by blocking when the buffer
270-- Concurrent lookahead streams are generally more efficient than
271-- 'ParallelT' and can work pretty efficiently even for smaller tasks because
272-- they do not necessarily use a separate thread for each task. So they should
273-- be preferred over 'ParallelT' especially when efficiency is a concern and
274-- simultaneous strict evaluation is not a requirement. 'ParallelT' is useful
275-- for cases when the streams are required to be evaluated simultaneously
276-- irrespective of how the consumer consumes them e.g. when we want to race
277-- two tasks and want to start both strictly at the same time or if we have
278-- timers in the parallel tasks and our results depend on the timers being
279-- started at the same time. We can say that 'ParallelT' is almost the same
280-- (modulo some implementation differences) as 'WAsyncT' when the latter is
281-- used with unlimited lookahead and zero latency in initiating lookahead.
284-- main = ('toList' . 'parallely' $ (fromFoldable [1,2]) \<> (fromFoldable [3,4])) >>= print
290-- When streams with more than one element are merged, it yields whichever
291-- stream yields first without any bias, unlike the 'Async' style streams.
293-- Any exceptions generated by a constituent stream are propagated to the
294-- output stream. The output and exceptions from a single stream are guaranteed
295-- to arrive in the same order in the resulting stream as they were generated
296-- in the input stream. However, the relative ordering of elements from
297-- different streams in the resulting stream can vary depending on scheduling
298-- and generation delays.
300-- Similarly, the 'Monad' instance of 'ParallelT' runs /all/ iterations
301-- of the loop concurrently.
305-- import qualified "Streamly.Prelude" as S
306-- import Control.Concurrent
308-- main = 'runStream' . 'parallely' $ do
309-- n <- return 3 \<\> return 2 \<\> return 1
311-- threadDelay (n * 1000000)
312-- myThreadId >>= \\tid -> putStrLn (show tid ++ ": Delay " ++ show n)
315-- ThreadId 40: Delay 1
316-- ThreadId 39: Delay 2
317-- ThreadId 38: Delay 3
320-- Note that parallel composition can only combine a finite number of
321-- streams as it needs to retain state for each unfinished stream.
324newtype ParallelT m a = ParallelT {getParallelT :: Stream m a}
325 deriving (MonadTrans)
327-- | A parallely composing IO stream of elements of type @a@.
328-- See 'ParallelT' documentation for more details.
331type Parallel a = ParallelT IO a
333-- | Fix the type of a polymorphic stream as 'ParallelT'.
336parallely :: IsStream t => ParallelT m a -> t m a
339instance IsStream ParallelT where
340 toStream = getParallelT
341 fromStream = ParallelT
344 {-# SPECIALIZE consM :: IO a -> ParallelT IO a -> ParallelT IO a #-}
345 consM m r = fromStream $ consMParallel m (toStream r)
348 {-# SPECIALIZE (|:) :: IO a -> ParallelT IO a -> ParallelT IO a #-}
351------------------------------------------------------------------------------
353------------------------------------------------------------------------------
355instance MonadAsync m => Semigroup (ParallelT m a) where
358------------------------------------------------------------------------------
360------------------------------------------------------------------------------
362instance MonadAsync m => Monoid (ParallelT m a) where
366------------------------------------------------------------------------------
368------------------------------------------------------------------------------
370instance MonadAsync m => Monad (ParallelT m) where
373 = ParallelT $ K.bindWith parallelStream m (getParallelT . f)
375------------------------------------------------------------------------------
377------------------------------------------------------------------------------
379MONAD_APPLICATIVE_INSTANCE(ParallelT,MONADPARALLEL)
380MONAD_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