2{-# LANGUAGE ConstraintKinds #-}
3{-# LANGUAGE FlexibleContexts #-}
4{-# LANGUAGE FlexibleInstances #-}
5{-# LANGUAGE GeneralizedNewtypeDeriving#-}
6{-# LANGUAGE InstanceSigs #-}
7{-# LANGUAGE MultiParamTypeClasses #-}
8{-# LANGUAGE UndecidableInstances #-} -- XXX
13-- Module : Streamly.Streams.Parallel
14-- Copyright : (c) 2017 Harendra Kumar
17-- Maintainer : streamly@composewell.com
18-- Stability : experimental
22module Streamly.Streams.Parallel
32 -- * Function application
41import Control.Concurrent (myThreadId)
42import Control.Exception (SomeException(..), throwIO)
43import Control.Monad (ap)
44import Control.Monad.Base (MonadBase(..), liftBaseDefault)
45import Control.Monad.Catch (MonadThrow, throwM)
46-- import Control.Monad.Error.Class (MonadError(..))
47import Control.Monad.IO.Class (MonadIO(..))
48import Control.Monad.Reader.Class (MonadReader(..))
49import Control.Monad.State.Class (MonadState(..))
50import Control.Monad.Trans.Class (MonadTrans(lift))
51import Data.Functor (void)
52import Data.IORef (readIORef, writeIORef, newIORef)
53import Data.Maybe (fromJust)
54#if __GLASGOW_HASKELL__ < 808
55import Data.Semigroup (Semigroup(..))
57import Prelude hiding (map)
59import qualified Data.Set as Set
61import Streamly.Streams.SVar (fromSVar, fromStreamVar)
62import Streamly.Streams.Serial (map)
63import Streamly.Streams.StreamK (IsStream(..), Stream, mkStream, foldStream,
64 foldStreamShared, adapt)
66import Streamly.Internal.Data.Atomics (atomicModifyIORefCAS_)
67import Streamly.Internal.Data.Time.Clock (Clock(..), getTime)
68import Streamly.Internal.Data.SVar hiding (handleChildException)
70import qualified Streamly.Streams.StreamK as K
71import qualified Streamly.Streams.StreamD as D
72import qualified Streamly.Internal.Data.SVar as SVar
73import qualified Streamly.Internal.Data.Fold as FL
75#include "Instances.hs"
77-------------------------------------------------------------------------------
79-------------------------------------------------------------------------------
81-------------------------------------------------------------------------------
82-- StreamK based worker routines
83-------------------------------------------------------------------------------
85{-# NOINLINE runOne #-}
88 => State Stream m a -> Stream m a -> Maybe WorkerInfo -> m ()
90 case getYieldLimit st of
92 Just _ -> runOneLimited st m0 winfo
97 liftIO $ decrementBufferLimit sv
98 foldStreamShared st yieldk single stop m
100 sv = fromJust $ streamVar st
103 incrementBufferLimit sv
105 sendit a = liftIO $ void $ send sv (ChildYield a)
106 single a = sendit a >> (liftIO $ sendStop sv winfo)
107 yieldk a r = sendit a >> go r
111 => State Stream m a -> Stream m a -> Maybe WorkerInfo -> m ()
112runOneLimited st m0 winfo = go m0
117 yieldLimitOk <- liftIO $ decrementYieldLimit sv
120 liftIO $ decrementBufferLimit sv
121 foldStreamShared st yieldk single stop m
123 liftIO $ cleanupSVarFromWorker sv
124 liftIO $ sendStop sv winfo
126 sv = fromJust $ streamVar st
129 incrementBufferLimit sv
130 incrementYieldLimit sv
132 sendit a = liftIO $ void $ send sv (ChildYield a)
133 single a = sendit a >> (liftIO $ sendStop sv winfo)
134 yieldk a r = sendit a >> go r
136-------------------------------------------------------------------------------
137-- StreamD based worker routines
138-------------------------------------------------------------------------------
140-- Using StreamD the worker stream producing code can fuse with the code to
141-- queue output to the SVar giving some perf boost.
143-- Note that StreamD can only be used in limited situations, specifically, we
144-- cannot implement joinStreamVarPar using this.
146{-# INLINE_NORMAL pushWorkerParD #-}
147pushWorkerParD :: MonadAsync m
148 => State t m a -> SVar t m a -> D.Stream m a -> m ()
149pushWorkerParD st sv xs =
150 if svarInspectMode sv
154 case getYieldLimit st of
155 Nothing -> doFork (work Nothing)
157 (SVar.handleChildException sv)
158 Just _ -> doFork (workLim Nothing)
160 (SVar.handleChildException sv)
165 {-# NOINLINE work #-}
166 work info = (D.runFold (FL.toParallelSVar sv info) xs)
168 {-# NOINLINE workLim #-}
169 workLim info = D.runFold (FL.toParallelSVarLimited sv info) xs
171 {-# NOINLINE forkWithDiag #-}
173 -- We do not use workerCount in case of ParallelVar but still there is
174 -- no harm in maintaining it correctly.
175 liftIO $ atomicModifyIORefCAS_ (workerCount sv) $ \n -> n + 1
177 -- This allocation matters when significant number of workers are being
178 -- sent. We allocate it only when needed. The overhead increases by 4x.
180 case yieldRateInfo sv of
181 Nothing -> return Nothing
182 Just _ -> liftIO $ do
184 t <- getTime Monotonic
185 lat <- newIORef (0, t)
186 return $ Just WorkerInfo
188 , workerYieldCount = cntRef
189 , workerLatencyStart = lat
192 case getYieldLimit st of
193 Nothing -> doFork (work winfo)
195 (SVar.handleChildException sv)
196 Just _ -> doFork (workLim winfo)
198 (SVar.handleChildException sv)
201-------------------------------------------------------------------------------
202-- Consing and appending a stream in parallel style
203-------------------------------------------------------------------------------
205-- Note that consing and appending requires StreamK as it would not scale well
206-- with StreamD unless we are only consing a very small number of streams or
207-- elements in a stream. StreamK allows us to manipulate control flow in a way
208-- which StreamD cannot allow. StreamK can make a jump without having to
209-- remember the past state.
211{-# NOINLINE forkSVarPar #-}
212forkSVarPar :: (IsStream t, MonadAsync m)
213 => SVarStopStyle -> t m a -> t m a -> t m a
214forkSVarPar ss m r = mkStream $ \st yld sng stp -> do
215 sv <- newParallelVar ss st
216 pushWorkerPar sv (runOne st{streamVar = Just sv} $ toStream m)
218 StopBy -> liftIO $ do
219 set <- readIORef (workerThreads sv)
220 writeIORef (svarStopBy sv) $ Set.elemAt 0 set
222 pushWorkerPar sv (runOne st{streamVar = Just sv} $ toStream r)
223 foldStream st yld sng stp (fromSVar sv)
225{-# INLINE joinStreamVarPar #-}
226joinStreamVarPar :: (IsStream t, MonadAsync m)
227 => SVarStyle -> SVarStopStyle -> t m a -> t m a -> t m a
228joinStreamVarPar style ss m1 m2 = mkStream $ \st yld sng stp ->
230 Just sv | svarStyle sv == style && svarStopStyle sv == ss -> do
231 pushWorkerPar sv (runOne st $ toStream m1)
232 foldStreamShared st yld sng stp m2
233 _ -> foldStreamShared st yld sng stp (forkSVarPar ss m1 m2)
235-------------------------------------------------------------------------------
237-------------------------------------------------------------------------------
239-- | XXX we can implement it more efficienty by directly implementing instead
240-- of combining streams using parallel.
241{-# INLINE consMParallel #-}
242{-# SPECIALIZE consMParallel :: IO a -> ParallelT IO a -> ParallelT IO a #-}
243consMParallel :: MonadAsync m => m a -> ParallelT m a -> ParallelT m a
244consMParallel m r = fromStream $ K.yieldM m `parallel` (toStream r)
246-- | Polymorphic version of the 'Semigroup' operation '<>' of 'ParallelT'
247-- Merges two streams concurrently.
250{-# INLINE parallel #-}
251parallel :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a
252parallel = joinStreamVarPar ParallelVar StopNone
254-- This is a co-parallel like combinator for streams, where first stream is the
255-- main stream and the rest are just supporting it, when the first ends
258-- | Like `parallel` but stops the output as soon as the first stream stops.
261{-# INLINE parallelFst #-}
262parallelFst :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a
263parallelFst = joinStreamVarPar ParallelVar StopBy
265-- This is a race like combinator for streams.
267-- | Like `parallel` but stops the output as soon as any of the two streams
271{-# INLINE parallelMin #-}
272parallelMin :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a
273parallelMin = joinStreamVarPar ParallelVar StopAny
275------------------------------------------------------------------------------
276-- Convert a stream to parallel
277------------------------------------------------------------------------------
279_mkParallelK :: (IsStream t, MonadAsync m) => t m a -> m (t m a)
281 sv <- newParallelVar StopNone defState
282 pushWorkerPar sv (runOne defState{streamVar = Just sv} $ toStream m)
285-- We may have to use it in higher order functions like concatMap, so use the
286-- Normal phase for inlining.
287{-# INLINE_NORMAL mkParallel #-}
288mkParallel :: (IsStream t, MonadAsync m)
289 => State Stream m a -> t m a -> m (t m a)
291 sv <- newParallelVar StopNone defState
292 pushWorkerParD st sv (D.toStreamD m)
295------------------------------------------------------------------------------
296-- Stream to stream concurrent function application
297------------------------------------------------------------------------------
299{-# INLINE _applyParallelK #-}
300_applyParallelK :: (IsStream t, MonadAsync m)
301 => (t m a -> t m b) -> t m a -> t m b
302_applyParallelK f m = mkStream $ \st yld sng stp -> do
303 sv <- newParallelVar StopNone (adaptState st)
304 pushWorkerPar sv (runOne st{streamVar = Just sv} (toStream m))
305 foldStream st yld sng stp $ f $ fromSVar sv
307-- XXX need to implement fromSVar in direct style for fused generation from
309{-# INLINE applyParallelD #-}
310applyParallelD :: (IsStream t, MonadAsync m)
311 => (t m a -> t m b) -> t m a -> t m b
312applyParallelD f m = mkStream $ \st yld sng stp -> do
313 sv <- newParallelVar StopNone (adaptState st)
314 pushWorkerParD (adaptState st) sv (D.toStreamD m)
315 foldStream st yld sng stp $ f $ fromSVar sv
317{-# INLINE applyParallel #-}
318applyParallel :: (IsStream t, MonadAsync m)
319 => (t m a -> t m b) -> t m a -> t m b
320applyParallel = applyParallelD
322------------------------------------------------------------------------------
323-- Stream runner concurrent function application
324------------------------------------------------------------------------------
326{-# INLINE _foldParallelK #-}
327_foldParallelK :: (IsStream t, MonadAsync m) => (t m a -> m b) -> t m a -> m b
328_foldParallelK f m = do
329 sv <- newParallelVar StopNone defState
330 pushWorkerPar sv (runOne defState{streamVar = Just sv} $ toStream m)
333-- XXX need to implement fromSVar in direct style for fused generation from
335{-# INLINE foldParallelD #-}
336foldParallelD :: (IsStream t, MonadAsync m) => (t m a -> m b) -> t m a -> m b
337foldParallelD f m = do
338 sv <- newParallelVar StopNone defState
339 pushWorkerParD defState sv (D.toStreamD m)
342{-# INLINE foldParallel #-}
343foldParallel :: (IsStream t, MonadAsync m) => (t m a -> m b) -> t m a -> m b
344foldParallel = foldParallelD
346------------------------------------------------------------------------------
348------------------------------------------------------------------------------
350{-# INLINE teeToSVar #-}
351teeToSVar :: (IsStream t, MonadAsync m) => SVar Stream m a -> t m a -> t m a
352teeToSVar svr m = mkStream $ \st yld sng stp -> do
353 foldStreamShared st yld sng stp (go svr m)
357 go sv m0 = mkStream $ \st yld sng stp -> do
360 incrementBufferLimit sv
362 -- XXX wait for Stop response event on exception channel,
363 -- drain the exception channel.
365 sendit a = liftIO $ do
366 -- XXX check for exceptions before decrement so that we do not
367 -- block forever if the child already exited with an exception.
368 decrementBufferLimit sv
369 void $ send sv (ChildYield a)
370 single a = sendit a >> (liftIO $ sendStop sv Nothing) >> sng a
371 yieldk a r = sendit a >> yld a (go sv r)
372 in foldStreamShared st yieldk single stop m0
374-- In case of folds the roles of worker and parent on an SVar are reversed. The
375-- parent stream pushes values to an SVar instead of pulling from it and a
376-- worker thread running the fold pulls from the SVar and folds the stream. We
377-- keep a separate channel for pushing exceptions in the reverse direction i.e.
378-- from the fold to the parent stream.
380-- NOTE: If we use fromSVar here it will kill the main computation (the parent)
381-- when the SVar goes away so we use fromStreamVar instead.
383{-# NOINLINE handleChildException #-}
384handleChildException :: SVar t m a -> SomeException -> IO ()
385handleChildException _sv e = do
387 -- void $ sendReverse sv (ChildStop tid (Just e))
390-- | Redirect a copy of the stream to a supplied fold and run it concurrently
391-- in an independent thread. The fold may buffer some elements. The buffer size
392-- is determined by the prevailing 'maxBuffer' setting.
397-- -----stream m a ---------------stream m a-----
402-- > S.drain $ S.tapAsync (S.mapM_ print) (S.enumerateFromTo 1 2)
407-- Exceptions from the concurrently running fold are propagated to the current
408-- computation. Note that, because of buffering in the fold, exceptions may be
409-- delayed and may not correspond to the current element being processed in the
410-- parent stream, but we guarantee that the tap finishes and all exceptions
411-- from it are drained before the parent stream stops.
414-- Compare with 'tap'.
417{-# INLINE tapAsync #-}
418tapAsync :: (IsStream t, MonadAsync m) => (t m a -> m b) -> t m a -> t m a
419tapAsync f m = mkStream $ \st yld sng stp -> do
420 -- Buffer size for the SVar is derived from the current state
421 sv <- newParallelVar StopNone (adaptState st)
422 -- XXX exception handling
423 -- XXX if we terminate due to an exception, do we need to actively
424 -- terminate the fold?
425 liftIO myThreadId >>= modifyThread sv
426 void $ doFork (void $ f $ fromStream $ fromStreamVar sv)
427 (svarMrun sv) (handleChildException sv)
428 foldStreamShared st yld sng stp (teeToSVar sv m)
430------------------------------------------------------------------------------
431-- Concurrent Application
432------------------------------------------------------------------------------
440-- | Parallel function application operator for streams; just like the regular
441-- function application operator '$' except that it is concurrent. The
442-- following code prints a value every second even though each stage adds a 1
448-- S.mapM (\\x -> threadDelay 1000000 >> print x)
449-- |$ S.repeatM (threadDelay 1000000 >> return 1)
456(|$) :: (IsStream t, MonadAsync m) => (t m a -> t m b) -> t m a -> t m b
457f |$ x = applyParallel f x
459-- | Parallel reverse function application operator for streams; just like the
460-- regular reverse function application operator '&' except that it is
465-- S.repeatM (threadDelay 1000000 >> return 1)
466-- |& S.mapM (\\x -> threadDelay 1000000 >> print x)
473(|&) :: (IsStream t, MonadAsync m) => t m a -> (t m a -> t m b) -> t m b
476-- | Parallel function application operator; applies a @run@ or @fold@ function
477-- to a stream such that the fold consumer and the stream producer run in
478-- parallel. A @run@ or @fold@ function reduces the stream to a value in the
479-- underlying monad. The @.@ at the end of the operator is a mnemonic for
480-- termination of the stream.
483-- S.foldlM' (\\_ a -> threadDelay 1000000 >> print a) ()
484-- |$. S.repeatM (threadDelay 1000000 >> return 1)
491(|$.) :: (IsStream t, MonadAsync m) => (t m a -> m b) -> t m a -> m b
492f |$. x = foldParallel f x
494-- | Parallel reverse function application operator for applying a run or fold
495-- functions to a stream. Just like '|$.' except that the operands are reversed.
498-- S.repeatM (threadDelay 1000000 >> return 1)
499-- |&. S.foldlM' (\\_ a -> threadDelay 1000000 >> print a) ()
506(|&.) :: (IsStream t, MonadAsync m) => t m a -> (t m a -> m b) -> m b
509------------------------------------------------------------------------------
511------------------------------------------------------------------------------
513-- | Async composition with strict concurrent execution of all streams.
515-- The 'Semigroup' instance of 'ParallelT' executes both the streams
516-- concurrently without any delay or without waiting for the consumer demand
517-- and /merges/ the results as they arrive. If the consumer does not consume
518-- the results, they are buffered upto a configured maximum, controlled by the
519-- 'maxBuffer' primitive. If the buffer becomes full the concurrent tasks will
520-- block until there is space in the buffer.
522-- Both 'WAsyncT' and 'ParallelT', evaluate the constituent streams fairly in a
523-- round robin fashion. The key difference is that 'WAsyncT' might wait for the
524-- consumer demand before it executes the tasks whereas 'ParallelT' starts
525-- executing all the tasks immediately without waiting for the consumer demand.
526-- For 'WAsyncT' the 'maxThreads' limit applies whereas for 'ParallelT' it does
527-- not apply. In other words, 'WAsyncT' can be lazy whereas 'ParallelT' is
530-- 'ParallelT' is useful for cases when the streams are required to be
531-- evaluated simultaneously irrespective of how the consumer consumes them e.g.
532-- when we want to race two tasks and want to start both strictly at the same
533-- time or if we have timers in the parallel tasks and our results depend on
534-- the timers being started at the same time. If we do not have such
535-- requirements then 'AsyncT' or 'AheadT' are recommended as they can be more
536-- efficient than 'ParallelT'.
539-- main = ('toList' . 'parallely' $ (fromFoldable [1,2]) \<> (fromFoldable [3,4])) >>= print
545-- When streams with more than one element are merged, it yields whichever
546-- stream yields first without any bias, unlike the 'Async' style streams.
548-- Any exceptions generated by a constituent stream are propagated to the
549-- output stream. The output and exceptions from a single stream are guaranteed
550-- to arrive in the same order in the resulting stream as they were generated
551-- in the input stream. However, the relative ordering of elements from
552-- different streams in the resulting stream can vary depending on scheduling
553-- and generation delays.
555-- Similarly, the 'Monad' instance of 'ParallelT' runs /all/ iterations
556-- of the loop concurrently.
560-- import qualified "Streamly.Prelude" as S
561-- import Control.Concurrent
563-- main = 'drain' . 'parallely' $ do
564-- n <- return 3 \<\> return 2 \<\> return 1
566-- threadDelay (n * 1000000)
567-- myThreadId >>= \\tid -> putStrLn (show tid ++ ": Delay " ++ show n)
570-- ThreadId 40: Delay 1
571-- ThreadId 39: Delay 2
572-- ThreadId 38: Delay 3
575-- Note that parallel composition can only combine a finite number of
576-- streams as it needs to retain state for each unfinished stream.
578-- /Since: 0.7.0 (maxBuffer applies to ParallelT streams)/
581newtype ParallelT m a = ParallelT {getParallelT :: Stream m a}
582 deriving (MonadTrans)
584-- | A parallely composing IO stream of elements of type @a@.
585-- See 'ParallelT' documentation for more details.
588type Parallel = ParallelT IO
590-- | Fix the type of a polymorphic stream as 'ParallelT'.
593parallely :: IsStream t => ParallelT m a -> t m a
596instance IsStream ParallelT where
597 toStream = getParallelT
598 fromStream = ParallelT
601 {-# SPECIALIZE consM :: IO a -> ParallelT IO a -> ParallelT IO a #-}
602 consM = consMParallel
605 {-# SPECIALIZE (|:) :: IO a -> ParallelT IO a -> ParallelT IO a #-}
608------------------------------------------------------------------------------
610------------------------------------------------------------------------------
612{-# INLINE mappendParallel #-}
613{-# SPECIALIZE mappendParallel :: ParallelT IO a -> ParallelT IO a -> ParallelT IO a #-}
614mappendParallel :: MonadAsync m => ParallelT m a -> ParallelT m a -> ParallelT m a
615mappendParallel m1 m2 = fromStream $ parallel (toStream m1) (toStream m2)
617instance MonadAsync m => Semigroup (ParallelT m a) where
618 (<>) = mappendParallel
620------------------------------------------------------------------------------
622------------------------------------------------------------------------------
624instance MonadAsync m => Monoid (ParallelT m a) where
628------------------------------------------------------------------------------
630------------------------------------------------------------------------------
632{-# INLINE bindParallel #-}
633{-# SPECIALIZE bindParallel :: ParallelT IO a -> (a -> ParallelT IO b) -> ParallelT IO b #-}
634bindParallel :: MonadAsync m => ParallelT m a -> (a -> ParallelT m b) -> ParallelT m b
635bindParallel m f = fromStream $ K.bindWith parallel (K.adapt m) (\a -> K.adapt $ f a)
637instance MonadAsync m => Monad (ParallelT m) where
641------------------------------------------------------------------------------
643------------------------------------------------------------------------------
645MONAD_APPLICATIVE_INSTANCE(ParallelT,MONADPARALLEL)
646MONAD_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