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