on dbcf536 help sign in
383 lines 14 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
27 -- * Function application
28 , mkParallel
29 , (|$)
30 , (|&)
31 , (|$.)
32 , (|&.)
33 )
34where
36import Control.Monad (ap)
37import Control.Monad.Base (MonadBase(..), liftBaseDefault)
38import Control.Monad.Catch (MonadThrow, throwM)
39-- import Control.Monad.Error.Class (MonadError(..))
40import Control.Monad.IO.Class (MonadIO(..))
41import Control.Monad.Reader.Class (MonadReader(..))
42import Control.Monad.State.Class (MonadState(..))
43import Control.Monad.Trans.Class (MonadTrans(lift))
44import Data.Functor (void)
45import Data.Maybe (fromJust)
46import Data.Semigroup (Semigroup(..))
47import Prelude hiding (map)
49import Streamly.Streams.SVar (fromSVar)
50import Streamly.Streams.Serial (map)
51import Streamly.SVar
52import Streamly.Streams.StreamK (IsStream(..), Stream(..), adapt)
53import qualified Streamly.Streams.StreamK as K
55#include "Instances.hs"
57-------------------------------------------------------------------------------
58-- Parallel
59-------------------------------------------------------------------------------
61{-# NOINLINE runOne #-}
62runOne
63 :: MonadIO m
64 => State Stream m a -> Stream m a -> Maybe WorkerInfo -> m ()
65runOne st m winfo = unStream m st stop single yieldk
67 where
69 sv = fromJust $ streamVar st
70 mrun = runInIO $ svarMrun sv
72 withLimitCheck action = do
73 yieldLimitOk <- liftIO $ decrementYieldLimitPost sv
74 if yieldLimitOk
75 then action
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)
87 >> withLimitCheck (void $ liftIO $ mrun $ runOne st r winfo)
89{-# NOINLINE forkSVarPar #-}
90forkSVarPar :: MonadAsync m => Stream m a -> Stream m a -> Stream m a
91forkSVarPar m r = Stream $ \st stp sng yld -> do
92 sv <- newParallelVar st
93 pushWorkerPar sv (runOne st{streamVar = Just sv} m)
94 pushWorkerPar sv (runOne st{streamVar = Just sv} r)
95 unStream (fromSVar sv) (rstState st) stp sng yld
97{-# INLINE joinStreamVarPar #-}
98joinStreamVarPar :: MonadAsync m
99 => SVarStyle -> Stream m a -> Stream m a -> Stream m a
100joinStreamVarPar style m1 m2 = Stream $ \st stp sng yld ->
101 case streamVar st of
102 Just sv | svarStyle sv == style -> do
103 pushWorkerPar sv (runOne st m1)
104 unStream m2 st stp sng yld
105 _ -> unStream (forkSVarPar m1 m2) st stp sng yld
107{-# INLINE parallelStream #-}
108parallelStream :: MonadAsync m => Stream m a -> Stream m a -> Stream m a
109parallelStream = joinStreamVarPar ParallelVar
111-- | XXX we can implement it more efficienty by directly implementing instead
112-- of combining streams using parallel.
113{-# INLINE consMParallel #-}
114consMParallel :: MonadAsync m => m a -> Stream m a -> Stream m a
115consMParallel m r = K.yieldM m `parallelStream` r
117-- | Polymorphic version of the 'Semigroup' operation '<>' of 'ParallelT'
118-- Merges two streams concurrently.
119--
120-- @since 0.2.0
121{-# INLINE parallel #-}
122parallel :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a
123parallel m1 m2 = fromStream $ Stream $ \st stp sng yld ->
124 unStream (parallelStream (toStream m1) (toStream m2))
125 st stp sng yld
127------------------------------------------------------------------------------
128-- Convert a stream to parallel
129------------------------------------------------------------------------------
131mkParallel :: (IsStream t, MonadAsync m) => t m a -> m (t m a)
132mkParallel m = do
133 sv <- newParallelVar defState
134 pushWorkerPar sv (runOne defState{streamVar = Just sv} $ toStream m)
135 return $ fromSVar sv
137------------------------------------------------------------------------------
138-- Stream to stream concurrent function application
139------------------------------------------------------------------------------
141{-# INLINE applyWith #-}
142applyWith :: (IsStream t, MonadAsync m) => (t m a -> t m b) -> t m a -> t m b
143applyWith f m = fromStream $ Stream $ \st stp sng yld -> do
144 sv <- newParallelVar (rstState st)
145 pushWorkerPar sv (runOne st{streamVar = Just sv} (toStream m))
146 unStream (toStream $ f $ fromSVar sv) (rstState st) stp sng yld
148------------------------------------------------------------------------------
149-- Stream runner concurrent function application
150------------------------------------------------------------------------------
152{-# INLINE runWith #-}
153runWith :: (IsStream t, MonadAsync m) => (t m a -> m b) -> t m a -> m b
154runWith f m = do
155 sv <- newParallelVar defState
156 pushWorkerPar sv (runOne defState{streamVar = Just sv} $ toStream m)
157 f $ fromSVar sv
159------------------------------------------------------------------------------
160-- Concurrent Application
161------------------------------------------------------------------------------
163infixr 0 |$
164infixr 0 |$.
166infixl 1 |&
167infixl 1 |&.
169-- | Parallel function application operator for streams; just like the regular
170-- function application operator '$' except that it is concurrent. The
171-- following code prints a value every second even though each stage adds a 1
172-- second delay.
173--
174--
175-- @
176-- runStream $
177-- S.mapM (\\x -> threadDelay 1000000 >> print x)
178-- |$ S.repeatM (threadDelay 1000000 >> return 1)
179-- @
180--
181-- /Concurrent/
182--
183-- @since 0.3.0
184{-# INLINE (|$) #-}
185(|$) :: (IsStream t, MonadAsync m) => (t m a -> t m b) -> t m a -> t m b
186f |$ x = applyWith f x
188-- | Parallel reverse function application operator for streams; just like the
189-- regular reverse function application operator '&' except that it is
190-- concurrent.
191--
192-- @
193-- runStream $
194-- S.repeatM (threadDelay 1000000 >> return 1)
195-- |& S.mapM (\\x -> threadDelay 1000000 >> print x)
196-- @
197--
198-- /Concurrent/
199--
200-- @since 0.3.0
201{-# INLINE (|&) #-}
202(|&) :: (IsStream t, MonadAsync m) => t m a -> (t m a -> t m b) -> t m b
203x |& f = f |$ x
205-- | Parallel function application operator; applies a @run@ or @fold@ function
206-- to a stream such that the fold consumer and the stream producer run in
207-- parallel. A @run@ or @fold@ function reduces the stream to a value in the
208-- underlying monad. The @.@ at the end of the operator is a mnemonic for
209-- termination of the stream.
210--
211-- @
212-- S.foldlM' (\\_ a -> threadDelay 1000000 >> print a) ()
213-- |$. S.repeatM (threadDelay 1000000 >> return 1)
214-- @
215--
216-- /Concurrent/
217--
218-- @since 0.3.0
219{-# INLINE (|$.) #-}
220(|$.) :: (IsStream t, MonadAsync m) => (t m a -> m b) -> t m a -> m b
221f |$. x = runWith f x
223-- | Parallel reverse function application operator for applying a run or fold
224-- functions to a stream. Just like '|$.' except that the operands are reversed.
225--
226-- @
227-- S.repeatM (threadDelay 1000000 >> return 1)
228-- |&. S.foldlM' (\\_ a -> threadDelay 1000000 >> print a) ()
229-- @
230--
231-- /Concurrent/
232--
233-- @since 0.3.0
234{-# INLINE (|&.) #-}
235(|&.) :: (IsStream t, MonadAsync m) => t m a -> (t m a -> m b) -> m b
236x |&. f = f |$. x
238------------------------------------------------------------------------------
239-- ParallelT
240------------------------------------------------------------------------------
242-- | Async composition with simultaneous traversal of all streams.
243--
244-- The Semigroup instance of 'ParallelT' concurrently /merges/ two streams,
245-- running both strictly concurrently and yielding elements from both streams
246-- as they arrive. When multiple streams are combined using 'ParallelT' each
247-- one is evaluated in its own thread and the results produced are presented in
248-- the combined stream on a first come first serve basis.
249--
250-- 'AsyncT' and 'WAsyncT' are /concurrent lookahead streams/ each with a
251-- specific type of consumption pattern (depth first or breadth first). Since
252-- they are lookahead, they may introduce certain default latency in starting
253-- more concurrent tasks for efficiency reasons or may put a default limitation
254-- on the resource consumption (e.g. number of concurrent threads for
255-- lookahead). If we look at the implementation detail, they both can share a
256-- pool of worker threads to evaluate the streams in the desired pattern and at
257-- the desired rate. However, 'ParallelT' uses a separate runtime thread to
258-- evaluate each stream.
259--
260-- 'WAsyncT' is similar to 'ParallelT', as both of them evaluate the
261-- constituent streams fairly in a round robin fashion.
262-- However, the key difference is that 'WAsyncT' is lazy or pull driven
263-- whereas 'ParallelT' is strict or push driven. 'ParallelT' immediately
264-- starts concurrent evaluation of both the streams (in separate threads) and
265-- later picks the results whereas 'WAsyncT' may wait for a certain latency
266-- threshold before initiating concurrent evaluation of the next stream. The
267-- concurrent scheduling of the next stream or the degree of concurrency is
268-- driven by the feedback from the consumer. In case of 'ParallelT' each stream
269-- is evaluated in a separate thread and results are /pushed/ to a shared
270-- output buffer, the evaluation rate is controlled by blocking when the buffer
271-- is full.
272--
273-- Concurrent lookahead streams are generally more efficient than
274-- 'ParallelT' and can work pretty efficiently even for smaller tasks because
275-- they do not necessarily use a separate thread for each task. So they should
276-- be preferred over 'ParallelT' especially when efficiency is a concern and
277-- simultaneous strict evaluation is not a requirement. 'ParallelT' is useful
278-- for cases when the streams are required to be evaluated simultaneously
279-- irrespective of how the consumer consumes them e.g. when we want to race
280-- two tasks and want to start both strictly at the same time or if we have
281-- timers in the parallel tasks and our results depend on the timers being
282-- started at the same time. We can say that 'ParallelT' is almost the same
283-- (modulo some implementation differences) as 'WAsyncT' when the latter is
284-- used with unlimited lookahead and zero latency in initiating lookahead.
285--
286-- @
287-- main = ('toList' . 'parallely' $ (fromFoldable [1,2]) \<> (fromFoldable [3,4])) >>= print
288-- @
289-- @
290-- [1,3,2,4]
291-- @
292--
293-- When streams with more than one element are merged, it yields whichever
294-- stream yields first without any bias, unlike the 'Async' style streams.
295--
296-- Any exceptions generated by a constituent stream are propagated to the
297-- output stream. The output and exceptions from a single stream are guaranteed
298-- to arrive in the same order in the resulting stream as they were generated
299-- in the input stream. However, the relative ordering of elements from
300-- different streams in the resulting stream can vary depending on scheduling
301-- and generation delays.
302--
303-- Similarly, the 'Monad' instance of 'ParallelT' runs /all/ iterations
304-- of the loop concurrently.
305--
306-- @
307-- import "Streamly"
308-- import qualified "Streamly.Prelude" as S
309-- import Control.Concurrent
310--
311-- main = 'runStream' . 'parallely' $ do
312-- n <- return 3 \<\> return 2 \<\> return 1
313-- S.yieldM $ do
314-- threadDelay (n * 1000000)
315-- myThreadId >>= \\tid -> putStrLn (show tid ++ ": Delay " ++ show n)
316-- @
317-- @
318-- ThreadId 40: Delay 1
319-- ThreadId 39: Delay 2
320-- ThreadId 38: Delay 3
321-- @
322--
323-- Note that parallel composition can only combine a finite number of
324-- streams as it needs to retain state for each unfinished stream.
325--
326-- @since 0.1.0
327newtype ParallelT m a = ParallelT {getParallelT :: Stream m a}
328 deriving (MonadTrans)
330-- | A parallely composing IO stream of elements of type @a@.
331-- See 'ParallelT' documentation for more details.
332--
333-- @since 0.2.0
334type Parallel = ParallelT IO
336-- | Fix the type of a polymorphic stream as 'ParallelT'.
337--
338-- @since 0.1.0
339parallely :: IsStream t => ParallelT m a -> t m a
340parallely = adapt
342instance IsStream ParallelT where
343 toStream = getParallelT
344 fromStream = ParallelT
346 {-# INLINE consM #-}
347 {-# SPECIALIZE consM :: IO a -> ParallelT IO a -> ParallelT IO a #-}
348 consM m r = fromStream $ consMParallel m (toStream r)
350 {-# INLINE (|:) #-}
351 {-# SPECIALIZE (|:) :: IO a -> ParallelT IO a -> ParallelT IO a #-}
352 (|:) = consM
354------------------------------------------------------------------------------
355-- Semigroup
356------------------------------------------------------------------------------
358instance MonadAsync m => Semigroup (ParallelT m a) where
359 (<>) = parallel
361------------------------------------------------------------------------------
362-- Monoid
363------------------------------------------------------------------------------
365instance MonadAsync m => Monoid (ParallelT m a) where
366 mempty = K.nil
367 mappend = (<>)
369------------------------------------------------------------------------------
370-- Monad
371------------------------------------------------------------------------------
373instance MonadAsync m => Monad (ParallelT m) where
374 return = pure
375 (ParallelT m) >>= f
376 = ParallelT $ K.bindWith parallelStream m (getParallelT . f)
378------------------------------------------------------------------------------
379-- Other instances
380------------------------------------------------------------------------------
382MONAD_APPLICATIVE_INSTANCE(ParallelT,MONADPARALLEL)
383MONAD_COMMON_INSTANCES(ParallelT, MONADPARALLEL)