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 #-}
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
72 withLimitCheck action = do
73 yieldLimitOk <- liftIO $ decrementYieldLimitPost sv
76 else liftIO $ cleanupSVarFromWorker sv
78 stop = liftIO $ sendStop sv winfo
79 sendit a = liftIO $ sendYield sv winfo (ChildYield a)
80 single a = sendit a >> withLimitCheck stop
82 -- XXX there is no flow control in parallel case. We should perhaps use a
83 -- queue and queue it back on that and exit the thread when the outputQueue
84 -- overflows. Parallel is dangerous because it can accumulate unbounded
85 -- output in the buffer.
86 yieldk a r = void (sendit a) >> withLimitCheck (runOne st r winfo)
88{-# NOINLINE forkSVarPar #-}
89forkSVarPar :: MonadAsync m => Stream m a -> Stream m a -> Stream m a
90forkSVarPar m r = Stream $ \st stp sng yld -> do
91 sv <- newParallelVar st
92 pushWorkerPar sv (runOne st{streamVar = Just sv} m)
93 pushWorkerPar sv (runOne st{streamVar = Just sv} r)
94 (unStream (fromSVar sv)) (rstState st) stp sng yld
96{-# INLINE joinStreamVarPar #-}
97joinStreamVarPar :: MonadAsync m
98 => SVarStyle -> Stream m a -> Stream m a -> Stream m a
99joinStreamVarPar style m1 m2 = Stream $ \st stp sng yld ->
101 Just sv | svarStyle sv == style -> do
102 pushWorkerPar sv (runOne st m1)
103 unStream m2 st stp sng yld
104 _ -> unStream (forkSVarPar m1 m2) st stp sng yld
106{-# INLINE parallelStream #-}
107parallelStream :: MonadAsync m => Stream m a -> Stream m a -> Stream m a
108parallelStream = joinStreamVarPar ParallelVar
110-- | XXX we can implement it more efficienty by directly implementing instead
111-- of combining streams using parallel.
112{-# INLINE consMParallel #-}
113consMParallel :: MonadAsync m => m a -> Stream m a -> Stream m a
114consMParallel m r = K.yieldM m `parallelStream` r
116-- | Polymorphic version of the 'Semigroup' operation '<>' of 'ParallelT'
117-- Merges two streams concurrently.
120{-# INLINE parallel #-}
121parallel :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a
122parallel m1 m2 = fromStream $ Stream $ \st stp sng yld -> do
123 unStream (parallelStream (toStream m1) (toStream m2))
126------------------------------------------------------------------------------
127-- Convert a stream to parallel
128------------------------------------------------------------------------------
130mkParallel :: (IsStream t, MonadAsync m) => t m a -> m (t m a)
132 sv <- newParallelVar defState
133 pushWorkerPar sv (runOne defState{streamVar = Just sv} $ toStream m)
136------------------------------------------------------------------------------
137-- Stream to stream concurrent function application
138------------------------------------------------------------------------------
140{-# INLINE applyWith #-}
141applyWith :: (IsStream t, MonadAsync m) => (t m a -> t m b) -> t m a -> t m b
142applyWith f m = fromStream $ Stream $ \st stp sng yld -> do
143 sv <- newParallelVar (rstState st)
144 pushWorkerPar sv (runOne st{streamVar = Just sv} (toStream m))
145 unStream (toStream $ f $ fromSVar sv) (rstState st) stp sng yld
147------------------------------------------------------------------------------
148-- Stream runner concurrent function application
149------------------------------------------------------------------------------
151{-# INLINE runWith #-}
152runWith :: (IsStream t, MonadAsync m) => (t m a -> m b) -> t m a -> m b
154 sv <- newParallelVar defState
155 pushWorkerPar sv (runOne defState{streamVar = Just sv} $ toStream m)
158------------------------------------------------------------------------------
159-- Concurrent Application
160------------------------------------------------------------------------------
168-- | Parallel function application operator for streams; just like the regular
169-- function application operator '$' except that it is concurrent. The
170-- following code prints a value every second even though each stage adds a 1
176-- S.mapM (\\x -> threadDelay 1000000 >> print x)
177-- |$ S.repeatM (threadDelay 1000000 >> return 1)
184(|$) :: (IsStream t, MonadAsync m) => (t m a -> t m b) -> t m a -> t m b
185f |$ x = applyWith f x
187-- | Parallel reverse function application operator for streams; just like the
188-- regular reverse function application operator '&' except that it is
193-- S.repeatM (threadDelay 1000000 >> return 1)
194-- |& S.mapM (\\x -> threadDelay 1000000 >> print x)
201(|&) :: (IsStream t, MonadAsync m) => t m a -> (t m a -> t m b) -> t m b
204-- | Parallel function application operator; applies a @run@ or @fold@ function
205-- to a stream such that the fold consumer and the stream producer run in
206-- parallel. A @run@ or @fold@ function reduces the stream to a value in the
207-- underlying monad. The @.@ at the end of the operator is a mnemonic for
208-- termination of the stream.
211-- S.foldlM' (\\_ a -> threadDelay 1000000 >> print a) ()
212-- |$. S.repeatM (threadDelay 1000000 >> return 1)
219(|$.) :: (IsStream t, MonadAsync m) => (t m a -> m b) -> t m a -> m b
222-- | Parallel reverse function application operator for applying a run or fold
223-- functions to a stream. Just like '|$.' except that the operands are reversed.
226-- S.repeatM (threadDelay 1000000 >> return 1)
227-- |&. S.foldlM' (\\_ a -> threadDelay 1000000 >> print a) ()
234(|&.) :: (IsStream t, MonadAsync m) => t m a -> (t m a -> m b) -> m b
237------------------------------------------------------------------------------
239------------------------------------------------------------------------------
241-- | Async composition with simultaneous traversal of all streams.
243-- The Semigroup instance of 'ParallelT' concurrently /merges/ two streams,
244-- running both strictly concurrently and yielding elements from both streams
245-- as they arrive. When multiple streams are combined using 'ParallelT' each
246-- one is evaluated in its own thread and the results produced are presented in
247-- the combined stream on a first come first serve basis.
249-- 'AsyncT' and 'WAsyncT' are /concurrent lookahead streams/ each with a
250-- specific type of consumption pattern (depth first or breadth first). Since
251-- they are lookahead, they may introduce certain default latency in starting
252-- more concurrent tasks for efficiency reasons or may put a default limitation
253-- on the resource consumption (e.g. number of concurrent threads for
254-- lookahead). If we look at the implementation detail, they both can share a
255-- pool of worker threads to evaluate the streams in the desired pattern and at
256-- the desired rate. However, 'ParallelT' uses a separate runtime thread to
257-- evaluate each stream.
259-- 'WAsyncT' is similar to 'ParallelT', as both of them evaluate the
260-- constituent streams fairly in a round robin fashion.
261-- However, the key difference is that 'WAsyncT' is lazy or pull driven
262-- whereas 'ParallelT' is strict or push driven. 'ParallelT' immediately
263-- starts concurrent evaluation of both the streams (in separate threads) and
264-- later picks the results whereas 'WAsyncT' may wait for a certain latency
265-- threshold before initiating concurrent evaluation of the next stream. The
266-- concurrent scheduling of the next stream or the degree of concurrency is
267-- driven by the feedback from the consumer. In case of 'ParallelT' each stream
268-- is evaluated in a separate thread and results are /pushed/ to a shared
269-- output buffer, the evaluation rate is controlled by blocking when the buffer
272-- Concurrent lookahead streams are generally more efficient than
273-- 'ParallelT' and can work pretty efficiently even for smaller tasks because
274-- they do not necessarily use a separate thread for each task. So they should
275-- be preferred over 'ParallelT' especially when efficiency is a concern and
276-- simultaneous strict evaluation is not a requirement. 'ParallelT' is useful
277-- for cases when the streams are required to be evaluated simultaneously
278-- irrespective of how the consumer consumes them e.g. when we want to race
279-- two tasks and want to start both strictly at the same time or if we have
280-- timers in the parallel tasks and our results depend on the timers being
281-- started at the same time. We can say that 'ParallelT' is almost the same
282-- (modulo some implementation differences) as 'WAsyncT' when the latter is
283-- used with unlimited lookahead and zero latency in initiating lookahead.
286-- main = ('toList' . 'parallely' $ (fromFoldable [1,2]) \<> (fromFoldable [3,4])) >>= print
292-- When streams with more than one element are merged, it yields whichever
293-- stream yields first without any bias, unlike the 'Async' style streams.
295-- Any exceptions generated by a constituent stream are propagated to the
296-- output stream. The output and exceptions from a single stream are guaranteed
297-- to arrive in the same order in the resulting stream as they were generated
298-- in the input stream. However, the relative ordering of elements from
299-- different streams in the resulting stream can vary depending on scheduling
300-- and generation delays.
302-- Similarly, the 'Monad' instance of 'ParallelT' runs /all/ iterations
303-- of the loop concurrently.
307-- import qualified "Streamly.Prelude" as S
308-- import Control.Concurrent
310-- main = 'runStream' . 'parallely' $ do
311-- n <- return 3 \<\> return 2 \<\> return 1
313-- threadDelay (n * 1000000)
314-- myThreadId >>= \\tid -> putStrLn (show tid ++ ": Delay " ++ show n)
317-- ThreadId 40: Delay 1
318-- ThreadId 39: Delay 2
319-- ThreadId 38: Delay 3
322-- Note that parallel composition can only combine a finite number of
323-- streams as it needs to retain state for each unfinished stream.
326newtype ParallelT m a = ParallelT {getParallelT :: Stream m a}
327 deriving (MonadTrans)
329-- | A parallely composing IO stream of elements of type @a@.
330-- See 'ParallelT' documentation for more details.
333type Parallel a = ParallelT IO a
335-- | Fix the type of a polymorphic stream as 'ParallelT'.
338parallely :: IsStream t => ParallelT m a -> t m a
341instance IsStream ParallelT where
342 toStream = getParallelT
343 fromStream = ParallelT
346 {-# SPECIALIZE consM :: IO a -> ParallelT IO a -> ParallelT IO a #-}
347 consM m r = fromStream $ consMParallel m (toStream r)
350 {-# SPECIALIZE (|:) :: IO a -> ParallelT IO a -> ParallelT IO a #-}
353------------------------------------------------------------------------------
355------------------------------------------------------------------------------
357instance MonadAsync m => Semigroup (ParallelT m a) where
360------------------------------------------------------------------------------
362------------------------------------------------------------------------------
364instance MonadAsync m => Monoid (ParallelT m a) where
368------------------------------------------------------------------------------
370------------------------------------------------------------------------------
372instance MonadAsync m => Monad (ParallelT m) where
375 = ParallelT $ K.bindWith parallelStream m (getParallelT . f)
377------------------------------------------------------------------------------
379------------------------------------------------------------------------------
381MONAD_APPLICATIVE_INSTANCE(ParallelT,MONADPARALLEL)
382MONAD_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