2{-# LANGUAGE ConstraintKinds #-}
3{-# LANGUAGE FlexibleContexts #-}
4{-# LANGUAGE FlexibleInstances #-}
5{-# LANGUAGE GeneralizedNewtypeDeriving#-}
6{-# LANGUAGE InstanceSigs #-}
7{-# LANGUAGE MultiParamTypeClasses #-}
8{-# LANGUAGE UndecidableInstances #-} -- XXX
11-- Module : Streamly.Streams.Parallel
12-- Copyright : (c) 2017 Harendra Kumar
15-- Maintainer : harendra.kumar@gmail.com
16-- Stability : experimental
20module Streamly.Streams.Parallel
27 -- * Function application
36import Control.Monad (ap)
37import Control.Monad.Base (MonadBase(..), liftBaseDefault)
38import Control.Monad.Catch (MonadThrow, throwM)
39-- import Control.Monad.Error.Class (MonadError(..))
40import Control.Monad.IO.Class (MonadIO(..))
41import Control.Monad.Reader.Class (MonadReader(..))
42import Control.Monad.State.Class (MonadState(..))
43import Control.Monad.Trans.Class (MonadTrans(lift))
44import Data.Functor (void)
45import Data.Maybe (fromJust)
46import Data.Semigroup (Semigroup(..))
47import Prelude hiding (map)
49import Streamly.Streams.SVar (fromSVar)
50import Streamly.Streams.Serial (map)
52import Streamly.Streams.StreamK (IsStream(..), Stream, mkStream, foldStream,
53 foldStreamShared, 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 = foldStreamShared st yieldk single stop m
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 :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a
92forkSVarPar m r = mkStream $ \st yld sng stp -> do
93 sv <- newParallelVar st
94 pushWorkerPar sv (runOne st{streamVar = Just sv} $ toStream m)
95 pushWorkerPar sv (runOne st{streamVar = Just sv} $ toStream r)
96 foldStream st yld sng stp (fromSVar sv)
98{-# INLINE joinStreamVarPar #-}
99joinStreamVarPar :: (IsStream t, MonadAsync m)
100 => SVarStyle -> t m a -> t m a -> t m a
101joinStreamVarPar style m1 m2 = mkStream $ \st yld sng stp ->
103 Just sv | svarStyle sv == style -> do
104 pushWorkerPar sv (runOne st $ toStream m1)
105 foldStreamShared st yld sng stp m2
106 _ -> foldStreamShared st yld sng stp (forkSVarPar m1 m2)
108-- | XXX we can implement it more efficienty by directly implementing instead
109-- of combining streams using parallel.
110{-# INLINE consMParallel #-}
111consMParallel :: (IsStream t, MonadAsync m) => m a -> t m a -> t m a
112consMParallel m r = K.yieldM m `parallel` 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 = joinStreamVarPar ParallelVar
122------------------------------------------------------------------------------
123-- Convert a stream to parallel
124------------------------------------------------------------------------------
126mkParallel :: (IsStream t, MonadAsync m) => t m a -> m (t m a)
128 sv <- newParallelVar defState
129 pushWorkerPar sv (runOne defState{streamVar = Just sv} $ toStream m)
132------------------------------------------------------------------------------
133-- Stream to stream concurrent function application
134------------------------------------------------------------------------------
136{-# INLINE applyWith #-}
137applyWith :: (IsStream t, MonadAsync m) => (t m a -> t m b) -> t m a -> t m b
138applyWith f m = mkStream $ \st yld sng stp -> do
139 sv <- newParallelVar (adaptState st)
140 pushWorkerPar sv (runOne st{streamVar = Just sv} (toStream m))
141 foldStream st yld sng stp $ f $ fromSVar sv
143------------------------------------------------------------------------------
144-- Stream runner concurrent function application
145------------------------------------------------------------------------------
147{-# INLINE runWith #-}
148runWith :: (IsStream t, MonadAsync m) => (t m a -> m b) -> t m a -> m b
150 sv <- newParallelVar defState
151 pushWorkerPar sv (runOne defState{streamVar = Just sv} $ toStream m)
154------------------------------------------------------------------------------
155-- Concurrent Application
156------------------------------------------------------------------------------
164-- | Parallel function application operator for streams; just like the regular
165-- function application operator '$' except that it is concurrent. The
166-- following code prints a value every second even though each stage adds a 1
172-- S.mapM (\\x -> threadDelay 1000000 >> print x)
173-- |$ S.repeatM (threadDelay 1000000 >> return 1)
180(|$) :: (IsStream t, MonadAsync m) => (t m a -> t m b) -> t m a -> t m b
181f |$ x = applyWith f x
183-- | Parallel reverse function application operator for streams; just like the
184-- regular reverse function application operator '&' except that it is
189-- S.repeatM (threadDelay 1000000 >> return 1)
190-- |& S.mapM (\\x -> threadDelay 1000000 >> print x)
197(|&) :: (IsStream t, MonadAsync m) => t m a -> (t m a -> t m b) -> t m b
200-- | Parallel function application operator; applies a @run@ or @fold@ function
201-- to a stream such that the fold consumer and the stream producer run in
202-- parallel. A @run@ or @fold@ function reduces the stream to a value in the
203-- underlying monad. The @.@ at the end of the operator is a mnemonic for
204-- termination of the stream.
207-- S.foldlM' (\\_ a -> threadDelay 1000000 >> print a) ()
208-- |$. S.repeatM (threadDelay 1000000 >> return 1)
215(|$.) :: (IsStream t, MonadAsync m) => (t m a -> m b) -> t m a -> m b
218-- | Parallel reverse function application operator for applying a run or fold
219-- functions to a stream. Just like '|$.' except that the operands are reversed.
222-- S.repeatM (threadDelay 1000000 >> return 1)
223-- |&. S.foldlM' (\\_ a -> threadDelay 1000000 >> print a) ()
230(|&.) :: (IsStream t, MonadAsync m) => t m a -> (t m a -> m b) -> m b
233------------------------------------------------------------------------------
235------------------------------------------------------------------------------
237-- | Async composition with simultaneous traversal of all streams.
239-- The Semigroup instance of 'ParallelT' concurrently /merges/ two streams,
240-- running both strictly concurrently and yielding elements from both streams
241-- as they arrive. When multiple streams are combined using 'ParallelT' each
242-- one is evaluated in its own thread and the results produced are presented in
243-- the combined stream on a first come first serve basis.
245-- 'AsyncT' and 'WAsyncT' are /concurrent lookahead streams/ each with a
246-- specific type of consumption pattern (depth first or breadth first). Since
247-- they are lookahead, they may introduce certain default latency in starting
248-- more concurrent tasks for efficiency reasons or may put a default limitation
249-- on the resource consumption (e.g. number of concurrent threads for
250-- lookahead). If we look at the implementation detail, they both can share a
251-- pool of worker threads to evaluate the streams in the desired pattern and at
252-- the desired rate. However, 'ParallelT' uses a separate runtime thread to
253-- evaluate each stream.
255-- 'WAsyncT' is similar to 'ParallelT', as both of them evaluate the
256-- constituent streams fairly in a round robin fashion.
257-- However, the key difference is that 'WAsyncT' is lazy or pull driven
258-- whereas 'ParallelT' is strict or push driven. 'ParallelT' immediately
259-- starts concurrent evaluation of both the streams (in separate threads) and
260-- later picks the results whereas 'WAsyncT' may wait for a certain latency
261-- threshold before initiating concurrent evaluation of the next stream. The
262-- concurrent scheduling of the next stream or the degree of concurrency is
263-- driven by the feedback from the consumer. In case of 'ParallelT' each stream
264-- is evaluated in a separate thread and results are /pushed/ to a shared
265-- output buffer, the evaluation rate is controlled by blocking when the buffer
268-- Concurrent lookahead streams are generally more efficient than
269-- 'ParallelT' and can work pretty efficiently even for smaller tasks because
270-- they do not necessarily use a separate thread for each task. So they should
271-- be preferred over 'ParallelT' especially when efficiency is a concern and
272-- simultaneous strict evaluation is not a requirement. 'ParallelT' is useful
273-- for cases when the streams are required to be evaluated simultaneously
274-- irrespective of how the consumer consumes them e.g. when we want to race
275-- two tasks and want to start both strictly at the same time or if we have
276-- timers in the parallel tasks and our results depend on the timers being
277-- started at the same time. We can say that 'ParallelT' is almost the same
278-- (modulo some implementation differences) as 'WAsyncT' when the latter is
279-- used with unlimited lookahead and zero latency in initiating lookahead.
282-- main = ('toList' . 'parallely' $ (fromFoldable [1,2]) \<> (fromFoldable [3,4])) >>= print
288-- When streams with more than one element are merged, it yields whichever
289-- stream yields first without any bias, unlike the 'Async' style streams.
291-- Any exceptions generated by a constituent stream are propagated to the
292-- output stream. The output and exceptions from a single stream are guaranteed
293-- to arrive in the same order in the resulting stream as they were generated
294-- in the input stream. However, the relative ordering of elements from
295-- different streams in the resulting stream can vary depending on scheduling
296-- and generation delays.
298-- Similarly, the 'Monad' instance of 'ParallelT' runs /all/ iterations
299-- of the loop concurrently.
303-- import qualified "Streamly.Prelude" as S
304-- import Control.Concurrent
306-- main = 'runStream' . 'parallely' $ do
307-- n <- return 3 \<\> return 2 \<\> return 1
309-- threadDelay (n * 1000000)
310-- myThreadId >>= \\tid -> putStrLn (show tid ++ ": Delay " ++ show n)
313-- ThreadId 40: Delay 1
314-- ThreadId 39: Delay 2
315-- ThreadId 38: Delay 3
318-- Note that parallel composition can only combine a finite number of
319-- streams as it needs to retain state for each unfinished stream.
322newtype ParallelT m a = ParallelT {getParallelT :: Stream m a}
323 deriving (MonadTrans)
325-- | A parallely composing IO stream of elements of type @a@.
326-- See 'ParallelT' documentation for more details.
329type Parallel = ParallelT IO
331-- | Fix the type of a polymorphic stream as 'ParallelT'.
334parallely :: IsStream t => ParallelT m a -> t m a
337instance IsStream ParallelT where
338 toStream = getParallelT
339 fromStream = ParallelT
342 {-# SPECIALIZE consM :: IO a -> ParallelT IO a -> ParallelT IO a #-}
343 consM = consMParallel
346 {-# SPECIALIZE (|:) :: IO a -> ParallelT IO a -> ParallelT IO a #-}
349------------------------------------------------------------------------------
351------------------------------------------------------------------------------
353instance MonadAsync m => Semigroup (ParallelT m a) where
356------------------------------------------------------------------------------
358------------------------------------------------------------------------------
360instance MonadAsync m => Monoid (ParallelT m a) where
364------------------------------------------------------------------------------
366------------------------------------------------------------------------------
368instance MonadAsync m => Monad (ParallelT m) where
370 (>>=) = K.bindWith parallel
372------------------------------------------------------------------------------
374------------------------------------------------------------------------------
376MONAD_APPLICATIVE_INSTANCE(ParallelT,MONADPARALLEL)
377MONAD_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