on 46643d1 help sign in
542 lines 21 kB history raw
1{-# LANGUAGE CPP #-}
2{-# LANGUAGE ConstraintKinds #-}
3{-# LANGUAGE FlexibleContexts #-}
4{-# LANGUAGE FlexibleInstances #-}
5{-# LANGUAGE GeneralizedNewtypeDeriving#-}
6{-# LANGUAGE InstanceSigs #-}
7{-# LANGUAGE MultiParamTypeClasses #-}
8{-# LANGUAGE UndecidableInstances #-} -- XXX
10#include "inline.hs"
12-- |
13-- Module : Streamly.Internal.Data.Stream.Parallel
14-- Copyright : (c) 2017 Harendra Kumar
15--
16-- License : BSD3
17-- Maintainer : streamly@composewell.com
18-- Stability : experimental
19-- Portability : GHC
20--
21--
22module Streamly.Internal.Data.Stream.Parallel
23 (
24 ParallelT
25 , Parallel
26 , parallely
27 , parallel
28 , parallelFst
29 , parallelMin
30 , tapAsync
31 , mkParallel
32 , applyParallel
33 , foldParallel
34 )
35where
37import Control.Concurrent (myThreadId, takeMVar)
38import Control.Monad (when)
39import Control.Monad.Base (MonadBase(..), liftBaseDefault)
40import Control.Monad.Catch (MonadThrow, throwM)
41-- import Control.Monad.Error.Class (MonadError(..))
42import Control.Monad.IO.Class (MonadIO(..))
43import Control.Monad.Reader.Class (MonadReader(..))
44import Control.Monad.State.Class (MonadState(..))
45import Control.Monad.Trans.Class (MonadTrans(lift))
46import Data.Functor (void)
47import Data.IORef (readIORef, writeIORef)
48import Data.Maybe (fromJust)
49#if __GLASGOW_HASKELL__ < 808
50import Data.Semigroup (Semigroup(..))
51#endif
52import Prelude hiding (map)
54import qualified Data.Set as Set
56import Streamly.Internal.Data.Stream.SVar
57 (fromSVar, fromProducer, fromConsumer, pushToFold)
58import Streamly.Internal.Data.Stream.StreamK
59 (IsStream(..), Stream, mkStream, foldStream, foldStreamShared, adapt)
61import Streamly.Internal.Data.SVar
63import qualified Streamly.Internal.Data.Stream.StreamK as K
64import qualified Streamly.Internal.Data.Stream.StreamD as D
66#include "Instances.hs"
68-------------------------------------------------------------------------------
69-- Parallel
70-------------------------------------------------------------------------------
72-------------------------------------------------------------------------------
73-- StreamK based worker routines
74-------------------------------------------------------------------------------
76{-# NOINLINE runOne #-}
77runOne
78 :: MonadIO m
79 => State Stream m a -> Stream m a -> Maybe WorkerInfo -> m ()
80runOne st m0 winfo =
81 case getYieldLimit st of
82 Nothing -> go m0
83 Just _ -> runOneLimited st m0 winfo
85 where
87 go m = do
88 liftIO $ decrementBufferLimit sv
89 foldStreamShared st yieldk single stop m
91 sv = fromJust $ streamVar st
93 stop = liftIO $ do
94 incrementBufferLimit sv
95 sendStop sv winfo
96 sendit a = liftIO $ void $ send sv (ChildYield a)
97 single a = sendit a >> (liftIO $ sendStop sv winfo)
98 yieldk a r = sendit a >> go r
100runOneLimited
101 :: MonadIO m
102 => State Stream m a -> Stream m a -> Maybe WorkerInfo -> m ()
103runOneLimited st m0 winfo = go m0
105 where
107 go m = do
108 yieldLimitOk <- liftIO $ decrementYieldLimit sv
109 if yieldLimitOk
110 then do
111 liftIO $ decrementBufferLimit sv
112 foldStreamShared st yieldk single stop m
113 else do
114 liftIO $ cleanupSVarFromWorker sv
115 liftIO $ sendStop sv winfo
117 sv = fromJust $ streamVar st
119 stop = liftIO $ do
120 incrementBufferLimit sv
121 incrementYieldLimit sv
122 sendStop sv winfo
123 sendit a = liftIO $ void $ send sv (ChildYield a)
124 single a = sendit a >> (liftIO $ sendStop sv winfo)
125 yieldk a r = sendit a >> go r
127-------------------------------------------------------------------------------
128-- Consing and appending a stream in parallel style
129-------------------------------------------------------------------------------
131-- Note that consing and appending requires StreamK as it would not scale well
132-- with StreamD unless we are only consing a very small number of streams or
133-- elements in a stream. StreamK allows us to manipulate control flow in a way
134-- which StreamD cannot allow. StreamK can make a jump without having to
135-- remember the past state.
137{-# NOINLINE forkSVarPar #-}
138forkSVarPar :: (IsStream t, MonadAsync m)
139 => SVarStopStyle -> t m a -> t m a -> t m a
140forkSVarPar ss m r = mkStream $ \st yld sng stp -> do
141 sv <- newParallelVar ss st
142 pushWorkerPar sv (runOne st{streamVar = Just sv} $ toStream m)
143 case ss of
144 StopBy -> liftIO $ do
145 set <- readIORef (workerThreads sv)
146 writeIORef (svarStopBy sv) $ Set.elemAt 0 set
147 _ -> return ()
148 pushWorkerPar sv (runOne st{streamVar = Just sv} $ toStream r)
149 foldStream st yld sng stp (fromSVar sv)
151{-# INLINE joinStreamVarPar #-}
152joinStreamVarPar :: (IsStream t, MonadAsync m)
153 => SVarStyle -> SVarStopStyle -> t m a -> t m a -> t m a
154joinStreamVarPar style ss m1 m2 = mkStream $ \st yld sng stp ->
155 case streamVar st of
156 Just sv | svarStyle sv == style && svarStopStyle sv == ss -> do
157 -- Here, WE ARE IN THE WORKER/PRODUCER THREAD, we know that because
158 -- the SVar exists. We are running under runOne and the output we
159 -- produce ultimately will be sent to the SVar by runOne.
160 --
161 -- If we came here the worker/runOne is evaluating a `parallel`
162 -- combinator. In this case, we always fork a new worker for the
163 -- first component (m1) in the parallel composition and continue to
164 -- evaluate the second component (m2) in the current worker thread.
165 --
166 -- When m1 is serially composed, the worker would evaluate it
167 -- without any further forks and the resulting output is sent to
168 -- the SVar and the evaluation terminates. If m1 is a `parallel`
169 -- composition of two streams the worker would again recurses here.
170 --
171 -- Similarly, when m2 is serially composed it gets evaluated here
172 -- and the resulting output is sent to the SVar by the runOne
173 -- wrapper. When m2 is composed with `parallel` it will again
174 -- recurse here and so on until it finally terminates.
175 --
176 -- When we create a right associated expression using `parallel`,
177 -- then m1 would always terminate without further forks or
178 -- recursion into this routine, therefore, the worker returns
179 -- immediately after evaluating it. And m2 would continue to
180 -- fork/recurse, therefore, the current thread always recurses and
181 -- forks new workers one after the other. This is a tail recursive
182 -- style execution, m2, the recursive expression always executed at
183 -- the tail.
184 --
185 -- When the expression is left associated, the worker spawned would
186 -- get the forking/recursing responsibility and then again the
187 -- worker spawned by that worker would fork, thus creating layer
188 -- over layer of workers and a chain of threads leading to a very
189 -- inefficient execution.
190 pushWorkerPar sv (runOne st $ toStream m1)
191 foldStreamShared st yld sng stp m2
192 _ ->
193 -- Here WE ARE IN THE CONSUMER THREAD, we create a new SVar, fork
194 -- worker threads to execute m1 and m2 and this thread starts
195 -- pulling the stream from the SVar.
196 foldStreamShared st yld sng stp (forkSVarPar ss m1 m2)
198-------------------------------------------------------------------------------
199-- User facing APIs
200-------------------------------------------------------------------------------
202-- | XXX we can implement it more efficienty by directly implementing instead
203-- of combining streams using parallel.
204{-# INLINE consMParallel #-}
205{-# SPECIALIZE consMParallel :: IO a -> ParallelT IO a -> ParallelT IO a #-}
206consMParallel :: MonadAsync m => m a -> ParallelT m a -> ParallelT m a
207consMParallel m r = fromStream $ K.yieldM m `parallel` (toStream r)
209-- | Polymorphic version of the 'Semigroup' operation '<>' of 'ParallelT'
210-- Merges two streams concurrently.
211--
212-- @since 0.2.0
213{-# INLINE parallel #-}
214parallel :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a
215parallel = joinStreamVarPar ParallelVar StopNone
217-- This is a co-parallel like combinator for streams, where first stream is the
218-- main stream and the rest are just supporting it, when the first ends
219-- everything ends.
220--
221-- | Like `parallel` but stops the output as soon as the first stream stops.
222--
223-- @since 0.7.0
224{-# INLINE parallelFst #-}
225parallelFst :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a
226parallelFst = joinStreamVarPar ParallelVar StopBy
228-- This is a race like combinator for streams.
229--
230-- | Like `parallel` but stops the output as soon as any of the two streams
231-- stops.
232--
233-- @since 0.7.0
234{-# INLINE parallelMin #-}
235parallelMin :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a
236parallelMin = joinStreamVarPar ParallelVar StopAny
238------------------------------------------------------------------------------
239-- Convert a stream to parallel
240------------------------------------------------------------------------------
242mkParallel :: (IsStream t, MonadAsync m)
243 => State Stream m a -> t m a -> m (t m a)
244mkParallel st m = do
245 sv <- newParallelVar StopNone defState
246 -- pushWorkerPar sv (runOne st{streamVar = Just sv} $ toStream m)
247 D.toSVarParallel st sv $ D.toStreamD m
248 return $ fromSVar sv
250------------------------------------------------------------------------------
251-- Stream to stream concurrent function application
252------------------------------------------------------------------------------
254{-# INLINE applyParallel #-}
255applyParallel :: (IsStream t, MonadAsync m)
256 => (t m a -> t m b) -> t m a -> t m b
257applyParallel f m = mkStream $ \st yld sng stp -> do
258 sv <- newParallelVar StopNone (adaptState st)
259 -- pushWorkerPar sv (runOne st{streamVar = Just sv} (toStream m))
260 D.toSVarParallel (adaptState st) sv $ D.toStreamD m
261 foldStream st yld sng stp $ f $ fromSVar sv
263------------------------------------------------------------------------------
264-- Stream runner concurrent function application
265------------------------------------------------------------------------------
267{-# INLINE foldParallel #-}
268foldParallel :: (IsStream t, MonadAsync m) => (t m a -> m b) -> t m a -> m b
269foldParallel f m = do
270 sv <- newParallelVar StopNone defState
271 -- pushWorkerPar sv (runOne defState{streamVar = Just sv} $ toStream m)
272 D.toSVarParallel defState sv $ D.toStreamD m
273 f $ fromSVar sv
275------------------------------------------------------------------------------
276-- Clone and distribute a stream in parallel
277------------------------------------------------------------------------------
279-- Tap a stream and send the elements to the specified SVar in addition to
280-- yielding them again.
281--
282-- XXX this could be written in StreamD style for better efficiency with fusion.
283--
284{-# INLINE teeToSVar #-}
285teeToSVar :: (IsStream t, MonadAsync m) => SVar Stream m a -> t m a -> t m a
286teeToSVar svr m = mkStream $ \st yld sng stp -> do
287 foldStreamShared st yld sng stp (go False m)
289 where
291 go False m0 = mkStream $ \st yld _ stp -> do
292 let drain = do
293 -- In general, a Stop event would come equipped with the result
294 -- of the fold. It is not used here but it would be useful in
295 -- applicative and distribute.
296 done <- fromConsumer svr
297 when (not done) $ do
298 liftIO $ withDiagMVar svr "teeToSVar: waiting to drain"
299 $ takeMVar (outputDoorBellFromConsumer svr)
300 drain
302 stopFold = do
303 liftIO $ sendStop svr Nothing
304 -- drain/wait until a stop event arrives from the fold.
305 drain
307 stop = stopFold >> stp
308 single a = do
309 done <- pushToFold svr a
310 yld a (go done (K.nilM stopFold))
311 yieldk a r = pushToFold svr a >>= \done -> yld a (go done r)
312 in foldStreamShared st yieldk single stop m0
314 go True m0 = m0
316-- In case of folds the roles of worker and parent on an SVar are reversed. The
317-- parent stream pushes values to an SVar instead of pulling from it and a
318-- worker thread running the fold pulls from the SVar and folds the stream. We
319-- keep a separate channel for pushing exceptions in the reverse direction i.e.
320-- from the fold to the parent stream.
321--
322-- Note: If we terminate due to an exception, we do not actively terminate the
323-- fold. It gets cleaned up by the GC.
325-- | Create an SVar with a fold consumer that will fold any elements sent to it
326-- using the supplied fold function.
327{-# INLINE newFoldSVar #-}
328newFoldSVar :: (IsStream t, MonadAsync m)
329 => State Stream m a -> (t m a -> m b) -> m (SVar Stream m a)
330newFoldSVar stt f = do
331 -- Buffer size for the SVar is derived from the current state
332 sv <- newParallelVar StopAny (adaptState stt)
334 -- Add the producer thread-id to the SVar.
335 liftIO myThreadId >>= modifyThread sv
337 void $ doFork (void $ f $ fromStream $ fromProducer sv)
338 (svarMrun sv)
339 (handleFoldException sv)
340 return sv
342-- NOTE: In regular pull style streams, the consumer stream is pulling elements
343-- from the SVar and we have several workers producing elements and pushing to
344-- SVar. In case of folds, we, the parent stream driving the fold, are the
345-- stream producing worker, we start an SVar and start pushing to the SVar, the
346-- fold on the other side of the SVar is the consumer stream.
347--
348-- In the pull stream case exceptions are propagated from the producing workers
349-- to the consumer stream, the exceptions are propagated on the same channel as
350-- the produced stream elements. However, in case of push style folds the
351-- current stream itself is the worker and the fold is the consumer, in this
352-- case we have to propagate the exceptions from the consumer to the producer.
353-- This is reverse of the pull case and we need a reverse direction channel
354-- to propagate the exception.
355--
356-- | Redirect a copy of the stream to a supplied fold and run it concurrently
357-- in an independent thread. The fold may buffer some elements. The buffer size
358-- is determined by the prevailing 'maxBuffer' setting.
359--
360-- @
361-- Stream m a -> m b
362-- |
363-- -----stream m a ---------------stream m a-----
364--
365-- @
366--
367-- @
368-- > S.drain $ S.tapAsync (S.mapM_ print) (S.enumerateFromTo 1 2)
369-- 1
370-- 2
371-- @
372--
373-- Exceptions from the concurrently running fold are propagated to the current
374-- computation. Note that, because of buffering in the fold, exceptions may be
375-- delayed and may not correspond to the current element being processed in the
376-- parent stream, but we guarantee that before the parent stream stops the tap
377-- finishes and all exceptions from it are drained.
378--
379--
380-- Compare with 'tap'.
381--
382-- @since 0.7.0
383{-# INLINE tapAsync #-}
384tapAsync :: (IsStream t, MonadAsync m) => (t m a -> m b) -> t m a -> t m a
385tapAsync f m = mkStream $ \st yld sng stp -> do
386 sv <- newFoldSVar st f
387 foldStreamShared st yld sng stp (teeToSVar sv m)
389------------------------------------------------------------------------------
390-- ParallelT
391------------------------------------------------------------------------------
393-- | Async composition with strict concurrent execution of all streams.
394--
395-- The 'Semigroup' instance of 'ParallelT' executes both the streams
396-- concurrently without any delay or without waiting for the consumer demand
397-- and /merges/ the results as they arrive. If the consumer does not consume
398-- the results, they are buffered upto a configured maximum, controlled by the
399-- 'maxBuffer' primitive. If the buffer becomes full the concurrent tasks will
400-- block until there is space in the buffer.
401--
402-- Both 'WAsyncT' and 'ParallelT', evaluate the constituent streams fairly in a
403-- round robin fashion. The key difference is that 'WAsyncT' might wait for the
404-- consumer demand before it executes the tasks whereas 'ParallelT' starts
405-- executing all the tasks immediately without waiting for the consumer demand.
406-- For 'WAsyncT' the 'maxThreads' limit applies whereas for 'ParallelT' it does
407-- not apply. In other words, 'WAsyncT' can be lazy whereas 'ParallelT' is
408-- strict.
409--
410-- 'ParallelT' is useful for cases when the streams are required to be
411-- evaluated simultaneously irrespective of how the consumer consumes them e.g.
412-- when we want to race two tasks and want to start both strictly at the same
413-- time or if we have timers in the parallel tasks and our results depend on
414-- the timers being started at the same time. If we do not have such
415-- requirements then 'AsyncT' or 'AheadT' are recommended as they can be more
416-- efficient than 'ParallelT'.
417--
418-- @
419-- main = ('toList' . 'parallely' $ (fromFoldable [1,2]) \<> (fromFoldable [3,4])) >>= print
420-- @
421-- @
422-- [1,3,2,4]
423-- @
424--
425-- When streams with more than one element are merged, it yields whichever
426-- stream yields first without any bias, unlike the 'Async' style streams.
427--
428-- Any exceptions generated by a constituent stream are propagated to the
429-- output stream. The output and exceptions from a single stream are guaranteed
430-- to arrive in the same order in the resulting stream as they were generated
431-- in the input stream. However, the relative ordering of elements from
432-- different streams in the resulting stream can vary depending on scheduling
433-- and generation delays.
434--
435-- Similarly, the 'Monad' instance of 'ParallelT' runs /all/ iterations
436-- of the loop concurrently.
437--
438-- @
439-- import "Streamly"
440-- import qualified "Streamly.Prelude" as S
441-- import Control.Concurrent
442--
443-- main = 'drain' . 'parallely' $ do
444-- n <- return 3 \<\> return 2 \<\> return 1
445-- S.yieldM $ do
446-- threadDelay (n * 1000000)
447-- myThreadId >>= \\tid -> putStrLn (show tid ++ ": Delay " ++ show n)
448-- @
449-- @
450-- ThreadId 40: Delay 1
451-- ThreadId 39: Delay 2
452-- ThreadId 38: Delay 3
453-- @
454--
455-- Note that parallel composition can only combine a finite number of
456-- streams as it needs to retain state for each unfinished stream.
457--
458-- /Since: 0.7.0 (maxBuffer applies to ParallelT streams)/
459--
460-- /Since: 0.1.0/
461newtype ParallelT m a = ParallelT {getParallelT :: Stream m a}
462 deriving (MonadTrans)
464-- | A parallely composing IO stream of elements of type @a@.
465-- See 'ParallelT' documentation for more details.
466--
467-- @since 0.2.0
468type Parallel = ParallelT IO
470-- | Fix the type of a polymorphic stream as 'ParallelT'.
471--
472-- @since 0.1.0
473parallely :: IsStream t => ParallelT m a -> t m a
474parallely = adapt
476instance IsStream ParallelT where
477 toStream = getParallelT
478 fromStream = ParallelT
480 {-# INLINE consM #-}
481 {-# SPECIALIZE consM :: IO a -> ParallelT IO a -> ParallelT IO a #-}
482 consM = consMParallel
484 {-# INLINE (|:) #-}
485 {-# SPECIALIZE (|:) :: IO a -> ParallelT IO a -> ParallelT IO a #-}
486 (|:) = consM
488------------------------------------------------------------------------------
489-- Semigroup
490------------------------------------------------------------------------------
492{-# INLINE mappendParallel #-}
493{-# SPECIALIZE mappendParallel :: ParallelT IO a -> ParallelT IO a -> ParallelT IO a #-}
494mappendParallel :: MonadAsync m => ParallelT m a -> ParallelT m a -> ParallelT m a
495mappendParallel m1 m2 = fromStream $ parallel (toStream m1) (toStream m2)
497instance MonadAsync m => Semigroup (ParallelT m a) where
498 (<>) = mappendParallel
500------------------------------------------------------------------------------
501-- Monoid
502------------------------------------------------------------------------------
504instance MonadAsync m => Monoid (ParallelT m a) where
505 mempty = K.nil
506 mappend = (<>)
508------------------------------------------------------------------------------
509-- Applicative
510------------------------------------------------------------------------------
512{-# INLINE apParallel #-}
513{-# SPECIALIZE apParallel :: ParallelT IO (a -> b) -> ParallelT IO a -> ParallelT IO b #-}
514apParallel :: MonadAsync m => ParallelT m (a -> b) -> ParallelT m a -> ParallelT m b
515apParallel (ParallelT m1) (ParallelT m2) =
516 let f x1 = K.concatMapBy parallel (pure . x1) m2
517 in ParallelT $ K.concatMapBy parallel f m1
519instance (Monad m, MonadAsync m) => Applicative (ParallelT m) where
520 {-# INLINE pure #-}
521 pure = ParallelT . K.yield
522 {-# INLINE (<*>) #-}
523 (<*>) = apParallel
525------------------------------------------------------------------------------
526-- Monad
527------------------------------------------------------------------------------
529{-# INLINE bindParallel #-}
530{-# SPECIALIZE bindParallel :: ParallelT IO a -> (a -> ParallelT IO b) -> ParallelT IO b #-}
531bindParallel :: MonadAsync m => ParallelT m a -> (a -> ParallelT m b) -> ParallelT m b
532bindParallel m f = fromStream $ K.bindWith parallel (K.adapt m) (\a -> K.adapt $ f a)
534instance MonadAsync m => Monad (ParallelT m) where
535 return = pure
536 (>>=) = bindParallel
538------------------------------------------------------------------------------
539-- Other instances
540------------------------------------------------------------------------------
542MONAD_COMMON_INSTANCES(ParallelT, MONADPARALLEL)