on 0ae75fb help sign in
420 lines 16 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-- |
11-- Module : Streamly.Streams.Parallel
12-- Copyright : (c) 2017 Harendra Kumar
13--
14-- License : BSD3
15-- Maintainer : harendra.kumar@gmail.com
16-- Stability : experimental
17-- Portability : GHC
18--
19--
20module Streamly.Streams.Parallel
21 (
22 ParallelT
23 , Parallel
24 , parallely
25 , parallel
26 , parallelEndByFirst
27 , parallelEndByAny
29 -- * Function application
30 , mkParallel
31 , (|$)
32 , (|&)
33 , (|$.)
34 , (|&.)
35 )
36where
38import Control.Monad (ap)
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)
49import Data.Semigroup (Semigroup(..))
50import Prelude hiding (map)
52import qualified Data.Set as Set
54import Streamly.Streams.SVar (fromSVar)
55import Streamly.Streams.Serial (map)
56import Streamly.SVar
57import Streamly.Streams.StreamK (IsStream(..), Stream, mkStream, foldStream,
58 foldStreamShared, adapt)
59import qualified Streamly.Streams.StreamK as K
61#include "Instances.hs"
63-------------------------------------------------------------------------------
64-- Parallel
65-------------------------------------------------------------------------------
67{-# NOINLINE runOne #-}
68runOne
69 :: MonadIO m
70 => State Stream m a -> Stream m a -> Maybe WorkerInfo -> m ()
71runOne st m winfo = foldStreamShared st yieldk single stop m
73 where
75 sv = fromJust $ streamVar st
76 mrun = runInIO $ svarMrun sv
78 withLimitCheck action = do
79 yieldLimitOk <- liftIO $ decrementYieldLimitPost sv
80 if yieldLimitOk
81 then action
82 else liftIO $ cleanupSVarFromWorker sv
84 stop = liftIO $ sendStop sv winfo
85 sendit a = liftIO $ sendYield sv winfo (ChildYield a)
86 single a = sendit a >> withLimitCheck stop
88 -- XXX there is no flow control in parallel case. We should perhaps use a
89 -- queue and queue it back on that and exit the thread when the outputQueue
90 -- overflows. Parallel is dangerous because it can accumulate unbounded
91 -- output in the buffer.
92 yieldk a r = void (sendit a)
93 >> withLimitCheck (void $ liftIO $ mrun $ runOne st r winfo)
95{-# NOINLINE forkSVarPar #-}
96forkSVarPar :: (IsStream t, MonadAsync m)
97 => SVarStopStyle -> t m a -> t m a -> t m a
98forkSVarPar ss m r = mkStream $ \st yld sng stp -> do
99 sv <- newParallelVar ss st
100 pushWorkerPar sv (runOne st{streamVar = Just sv} $ toStream m)
101 case ss of
102 StopBy -> liftIO $ do
103 set <- readIORef (workerThreads sv)
104 writeIORef (svarStopBy sv) $ Set.elemAt 0 set
105 _ -> return ()
106 pushWorkerPar sv (runOne st{streamVar = Just sv} $ toStream r)
107 foldStream st yld sng stp (fromSVar sv)
109{-# INLINE joinStreamVarPar #-}
110joinStreamVarPar :: (IsStream t, MonadAsync m)
111 => SVarStyle -> SVarStopStyle -> t m a -> t m a -> t m a
112joinStreamVarPar style ss m1 m2 = mkStream $ \st yld sng stp ->
113 case streamVar st of
114 Just sv | svarStyle sv == style && svarStopStyle sv == ss -> do
115 pushWorkerPar sv (runOne st $ toStream m1)
116 foldStreamShared st yld sng stp m2
117 _ -> foldStreamShared st yld sng stp (forkSVarPar ss m1 m2)
119-- | XXX we can implement it more efficienty by directly implementing instead
120-- of combining streams using parallel.
121{-# INLINE consMParallel #-}
122{-# SPECIALIZE consMParallel :: IO a -> ParallelT IO a -> ParallelT IO a #-}
123consMParallel :: MonadAsync m => m a -> ParallelT m a -> ParallelT m a
124consMParallel m r = fromStream $ K.yieldM m `parallel` (toStream r)
126-- | Polymorphic version of the 'Semigroup' operation '<>' of 'ParallelT'
127-- Merges two streams concurrently.
128--
129-- @since 0.2.0
130{-# INLINE parallel #-}
131parallel :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a
132parallel = joinStreamVarPar ParallelVar StopNone
134-- This is a co-parallel like combinator for streams, where first stream is the
135-- main stream and the rest are just supporting it, when the first ends
136-- everything ends.
137--
138-- | Like `parallel` but stops the output as soon as the first stream stops.
139--
140-- @since 0.7.0
141{-# INLINE parallelEndByFirst #-}
142parallelEndByFirst :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a
143parallelEndByFirst = joinStreamVarPar ParallelVar StopBy
145-- This is a race like combinator for streams.
146--
147-- | Like `parallel` but stops the output as soon as any of the two streams
148-- stops.
149--
150-- @since 0.7.0
151{-# INLINE parallelEndByAny #-}
152parallelEndByAny :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a
153parallelEndByAny = joinStreamVarPar ParallelVar StopAny
155------------------------------------------------------------------------------
156-- Convert a stream to parallel
157------------------------------------------------------------------------------
159mkParallel :: (IsStream t, MonadAsync m) => t m a -> m (t m a)
160mkParallel m = do
161 sv <- newParallelVar StopNone defState
162 pushWorkerPar sv (runOne defState{streamVar = Just sv} $ toStream m)
163 return $ fromSVar sv
165------------------------------------------------------------------------------
166-- Stream to stream concurrent function application
167------------------------------------------------------------------------------
169{-# INLINE applyParallel #-}
170applyParallel :: (IsStream t, MonadAsync m) => (t m a -> t m b) -> t m a -> t m b
171applyParallel f m = mkStream $ \st yld sng stp -> do
172 sv <- newParallelVar StopNone (adaptState st)
173 pushWorkerPar sv (runOne st{streamVar = Just sv} (toStream m))
174 foldStream st yld sng stp $ f $ fromSVar sv
176------------------------------------------------------------------------------
177-- Stream runner concurrent function application
178------------------------------------------------------------------------------
180{-# INLINE foldParallel #-}
181foldParallel :: (IsStream t, MonadAsync m) => (t m a -> m b) -> t m a -> m b
182foldParallel f m = do
183 sv <- newParallelVar StopNone defState
184 pushWorkerPar sv (runOne defState{streamVar = Just sv} $ toStream m)
185 f $ fromSVar sv
187------------------------------------------------------------------------------
188-- Concurrent Application
189------------------------------------------------------------------------------
191infixr 0 |$
192infixr 0 |$.
194infixl 1 |&
195infixl 1 |&.
197-- | Parallel function application operator for streams; just like the regular
198-- function application operator '$' except that it is concurrent. The
199-- following code prints a value every second even though each stage adds a 1
200-- second delay.
201--
202--
203-- @
204-- drain $
205-- S.mapM (\\x -> threadDelay 1000000 >> print x)
206-- |$ S.repeatM (threadDelay 1000000 >> return 1)
207-- @
208--
209-- /Concurrent/
210--
211-- @since 0.3.0
212{-# INLINE (|$) #-}
213(|$) :: (IsStream t, MonadAsync m) => (t m a -> t m b) -> t m a -> t m b
214f |$ x = applyParallel f x
216-- | Parallel reverse function application operator for streams; just like the
217-- regular reverse function application operator '&' except that it is
218-- concurrent.
219--
220-- @
221-- drain $
222-- S.repeatM (threadDelay 1000000 >> return 1)
223-- |& S.mapM (\\x -> threadDelay 1000000 >> print x)
224-- @
225--
226-- /Concurrent/
227--
228-- @since 0.3.0
229{-# INLINE (|&) #-}
230(|&) :: (IsStream t, MonadAsync m) => t m a -> (t m a -> t m b) -> t m b
231x |& f = f |$ x
233-- | Parallel function application operator; applies a @run@ or @fold@ function
234-- to a stream such that the fold consumer and the stream producer run in
235-- parallel. A @run@ or @fold@ function reduces the stream to a value in the
236-- underlying monad. The @.@ at the end of the operator is a mnemonic for
237-- termination of the stream.
238--
239-- @
240-- S.foldlM' (\\_ a -> threadDelay 1000000 >> print a) ()
241-- |$. S.repeatM (threadDelay 1000000 >> return 1)
242-- @
243--
244-- /Concurrent/
245--
246-- @since 0.3.0
247{-# INLINE (|$.) #-}
248(|$.) :: (IsStream t, MonadAsync m) => (t m a -> m b) -> t m a -> m b
249f |$. x = foldParallel f x
251-- | Parallel reverse function application operator for applying a run or fold
252-- functions to a stream. Just like '|$.' except that the operands are reversed.
253--
254-- @
255-- S.repeatM (threadDelay 1000000 >> return 1)
256-- |&. S.foldlM' (\\_ a -> threadDelay 1000000 >> print a) ()
257-- @
258--
259-- /Concurrent/
260--
261-- @since 0.3.0
262{-# INLINE (|&.) #-}
263(|&.) :: (IsStream t, MonadAsync m) => t m a -> (t m a -> m b) -> m b
264x |&. f = f |$. x
266------------------------------------------------------------------------------
267-- ParallelT
268------------------------------------------------------------------------------
270-- | Async composition with simultaneous traversal of all streams.
271--
272-- The Semigroup instance of 'ParallelT' concurrently /merges/ two streams,
273-- running both strictly concurrently and yielding elements from both streams
274-- as they arrive. When multiple streams are combined using 'ParallelT' each
275-- one is evaluated in its own thread and the results produced are presented in
276-- the combined stream on a first come first serve basis.
277--
278-- 'AsyncT' and 'WAsyncT' are /concurrent lookahead streams/ each with a
279-- specific type of consumption pattern (depth first or breadth first). Since
280-- they are lookahead, they may introduce certain default latency in starting
281-- more concurrent tasks for efficiency reasons or may put a default limitation
282-- on the resource consumption (e.g. number of concurrent threads for
283-- lookahead). If we look at the implementation detail, they both can share a
284-- pool of worker threads to evaluate the streams in the desired pattern and at
285-- the desired rate. However, 'ParallelT' uses a separate runtime thread to
286-- evaluate each stream.
287--
288-- 'WAsyncT' is similar to 'ParallelT', as both of them evaluate the
289-- constituent streams fairly in a round robin fashion.
290-- However, the key difference is that 'WAsyncT' is lazy or pull driven
291-- whereas 'ParallelT' is strict or push driven. 'ParallelT' immediately
292-- starts concurrent evaluation of both the streams (in separate threads) and
293-- later picks the results whereas 'WAsyncT' may wait for a certain latency
294-- threshold before initiating concurrent evaluation of the next stream. The
295-- concurrent scheduling of the next stream or the degree of concurrency is
296-- driven by the feedback from the consumer. In case of 'ParallelT' each stream
297-- is evaluated in a separate thread and results are /pushed/ to a shared
298-- output buffer, the evaluation rate is controlled by blocking when the buffer
299-- is full.
300--
301-- Concurrent lookahead streams are generally more efficient than
302-- 'ParallelT' and can work pretty efficiently even for smaller tasks because
303-- they do not necessarily use a separate thread for each task. So they should
304-- be preferred over 'ParallelT' especially when efficiency is a concern and
305-- simultaneous strict evaluation is not a requirement. 'ParallelT' is useful
306-- for cases when the streams are required to be evaluated simultaneously
307-- irrespective of how the consumer consumes them e.g. when we want to race
308-- two tasks and want to start both strictly at the same time or if we have
309-- timers in the parallel tasks and our results depend on the timers being
310-- started at the same time. We can say that 'ParallelT' is almost the same
311-- (modulo some implementation differences) as 'WAsyncT' when the latter is
312-- used with unlimited lookahead and zero latency in initiating lookahead.
313--
314-- @
315-- main = ('toList' . 'parallely' $ (fromFoldable [1,2]) \<> (fromFoldable [3,4])) >>= print
316-- @
317-- @
318-- [1,3,2,4]
319-- @
320--
321-- When streams with more than one element are merged, it yields whichever
322-- stream yields first without any bias, unlike the 'Async' style streams.
323--
324-- Any exceptions generated by a constituent stream are propagated to the
325-- output stream. The output and exceptions from a single stream are guaranteed
326-- to arrive in the same order in the resulting stream as they were generated
327-- in the input stream. However, the relative ordering of elements from
328-- different streams in the resulting stream can vary depending on scheduling
329-- and generation delays.
330--
331-- Similarly, the 'Monad' instance of 'ParallelT' runs /all/ iterations
332-- of the loop concurrently.
333--
334-- @
335-- import "Streamly"
336-- import qualified "Streamly.Prelude" as S
337-- import Control.Concurrent
338--
339-- main = 'drain' . 'parallely' $ do
340-- n <- return 3 \<\> return 2 \<\> return 1
341-- S.yieldM $ do
342-- threadDelay (n * 1000000)
343-- myThreadId >>= \\tid -> putStrLn (show tid ++ ": Delay " ++ show n)
344-- @
345-- @
346-- ThreadId 40: Delay 1
347-- ThreadId 39: Delay 2
348-- ThreadId 38: Delay 3
349-- @
350--
351-- Note that parallel composition can only combine a finite number of
352-- streams as it needs to retain state for each unfinished stream.
353--
354-- @since 0.1.0
355newtype ParallelT m a = ParallelT {getParallelT :: Stream m a}
356 deriving (MonadTrans)
358-- | A parallely composing IO stream of elements of type @a@.
359-- See 'ParallelT' documentation for more details.
360--
361-- @since 0.2.0
362type Parallel = ParallelT IO
364-- | Fix the type of a polymorphic stream as 'ParallelT'.
365--
366-- @since 0.1.0
367parallely :: IsStream t => ParallelT m a -> t m a
368parallely = adapt
370instance IsStream ParallelT where
371 toStream = getParallelT
372 fromStream = ParallelT
374 {-# INLINE consM #-}
375 {-# SPECIALIZE consM :: IO a -> ParallelT IO a -> ParallelT IO a #-}
376 consM = consMParallel
378 {-# INLINE (|:) #-}
379 {-# SPECIALIZE (|:) :: IO a -> ParallelT IO a -> ParallelT IO a #-}
380 (|:) = consM
382------------------------------------------------------------------------------
383-- Semigroup
384------------------------------------------------------------------------------
386{-# INLINE mappendParallel #-}
387{-# SPECIALIZE mappendParallel :: ParallelT IO a -> ParallelT IO a -> ParallelT IO a #-}
388mappendParallel :: MonadAsync m => ParallelT m a -> ParallelT m a -> ParallelT m a
389mappendParallel m1 m2 = fromStream $ parallel (toStream m1) (toStream m2)
391instance MonadAsync m => Semigroup (ParallelT m a) where
392 (<>) = mappendParallel
394------------------------------------------------------------------------------
395-- Monoid
396------------------------------------------------------------------------------
398instance MonadAsync m => Monoid (ParallelT m a) where
399 mempty = K.nil
400 mappend = (<>)
402------------------------------------------------------------------------------
403-- Monad
404------------------------------------------------------------------------------
406{-# INLINE bindParallel #-}
407{-# SPECIALIZE bindParallel :: ParallelT IO a -> (a -> ParallelT IO b) -> ParallelT IO b #-}
408bindParallel :: MonadAsync m => ParallelT m a -> (a -> ParallelT m b) -> ParallelT m b
409bindParallel m f = fromStream $ K.bindWith parallel (K.adapt m) (\a -> K.adapt $ f a)
411instance MonadAsync m => Monad (ParallelT m) where
412 return = pure
413 (>>=) = bindParallel
415------------------------------------------------------------------------------
416-- Other instances
417------------------------------------------------------------------------------
419MONAD_APPLICATIVE_INSTANCE(ParallelT,MONADPARALLEL)
420MONAD_COMMON_INSTANCES(ParallelT, MONADPARALLEL)