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