@@ -7,6 +7,8 @@
7{-# LANGUAGE MultiParamTypeClasses #-}
7{-# LANGUAGE MultiParamTypeClasses #-}
8{-# LANGUAGE UndecidableInstances #-} -- XXX
8{-# LANGUAGE UndecidableInstances #-} -- XXX
11-- Module : Streamly.Streams.Parallel
13-- Module : Streamly.Streams.Parallel
12-- Copyright : (c) 2017 Harendra Kumar
14-- Copyright : (c) 2017 Harendra Kumar
@@ -47,7 +49,7 @@ import Control.Monad.Reader.Class (MonadReader(..))
47import Control.Monad.State.Class (MonadState(..))
49import Control.Monad.State.Class (MonadState(..))
48import Control.Monad.Trans.Class (MonadTrans(lift))
50import Control.Monad.Trans.Class (MonadTrans(lift))
49import Data.Functor (void)
51import Data.Functor (void)
50−import Data.IORef (readIORef, writeIORef)
52+import Data.IORef (readIORef, writeIORef, newIORef)
51import Data.Maybe (fromJust)
53import Data.Maybe (fromJust)
52#if __GLASGOW_HASKELL__ < 808
54#if __GLASGOW_HASKELL__ < 808
53import Data.Semigroup (Semigroup(..))
55import Data.Semigroup (Semigroup(..))
@@ -58,10 +60,17 @@ import qualified Data.Set as Set
59import Streamly.Streams.SVar (fromSVar, fromStreamVar)
61import Streamly.Streams.SVar (fromSVar, fromStreamVar)
60import Streamly.Streams.Serial (map)
62import Streamly.Streams.Serial (map)
61−import Streamly.Internal.Data.SVar
62import Streamly.Streams.StreamK (IsStream(..), Stream, mkStream, foldStream,
63import Streamly.Streams.StreamK (IsStream(..), Stream, mkStream, foldStream,
63 foldStreamShared, adapt)
64 foldStreamShared, adapt)
66+import Streamly.Internal.Data.Atomics (atomicModifyIORefCAS_)
67+import Streamly.Internal.Data.Time.Clock (Clock(..), getTime)
68+import Streamly.Internal.Data.SVar hiding (handleChildException)
64import qualified Streamly.Streams.StreamK as K
70import qualified Streamly.Streams.StreamK as K
71+import qualified Streamly.Streams.StreamD as D
72+import qualified Streamly.Internal.Data.SVar as SVar
73+import qualified Streamly.Internal.Data.Fold as FL
66#include "Instances.hs"
75#include "Instances.hs"
@@ -69,6 +78,10 @@ import qualified Streamly.Streams.StreamK as K
69-- Parallel
78-- Parallel
70-------------------------------------------------------------------------------
79-------------------------------------------------------------------------------
81+-------------------------------------------------------------------------------
82+-- StreamK based worker routines
83+-------------------------------------------------------------------------------
72{-# NOINLINE runOne #-}
85{-# NOINLINE runOne #-}
74 :: MonadIO m
87 :: MonadIO m
@@ -120,6 +133,81 @@ runOneLimited st m0 winfo = go m0
120 single a = sendit a >> (liftIO $ sendStop sv winfo)
133 single a = sendit a >> (liftIO $ sendStop sv winfo)
121 yieldk a r = sendit a >> go r
134 yieldk a r = sendit a >> go r
136+-------------------------------------------------------------------------------
137+-- StreamD based worker routines
138+-------------------------------------------------------------------------------
140+-- Using StreamD the worker stream producing code can fuse with the code to
141+-- queue output to the SVar giving some perf boost.
143+-- Note that StreamD can only be used in limited situations, specifically, we
144+-- cannot implement joinStreamVarPar using this.
146+{-# INLINE_NORMAL pushWorkerParD #-}
147+pushWorkerParD :: MonadAsync m
148+ => State t m a -> SVar t m a -> D.Stream m a -> m ()
149+pushWorkerParD st sv xs =
150+ if svarInspectMode sv
154+ case getYieldLimit st of
155+ Nothing -> doFork (work Nothing)
157+ (SVar.handleChildException sv)
158+ Just _ -> doFork (workLim Nothing)
160+ (SVar.handleChildException sv)
165+ {-# NOINLINE work #-}
166+ work info = (D.runFold (FL.toParallelSVar sv info) xs)
168+ {-# NOINLINE workLim #-}
169+ workLim info = D.runFold (FL.toParallelSVarLimited sv info) xs
171+ {-# NOINLINE forkWithDiag #-}
173+ -- We do not use workerCount in case of ParallelVar but still there is
174+ -- no harm in maintaining it correctly.
175+ liftIO $ atomicModifyIORefCAS_ (workerCount sv) $ \n -> n + 1
177+ -- This allocation matters when significant number of workers are being
178+ -- sent. We allocate it only when needed. The overhead increases by 4x.
180+ case yieldRateInfo sv of
181+ Nothing -> return Nothing
182+ Just _ -> liftIO $ do
183+ cntRef <- newIORef 0
184+ t <- getTime Monotonic
185+ lat <- newIORef (0, t)
186+ return $ Just WorkerInfo
187+ { workerYieldMax = 0
188+ , workerYieldCount = cntRef
189+ , workerLatencyStart = lat
192+ case getYieldLimit st of
193+ Nothing -> doFork (work winfo)
195+ (SVar.handleChildException sv)
196+ Just _ -> doFork (workLim winfo)
198+ (SVar.handleChildException sv)
201+-------------------------------------------------------------------------------
202+-- Consing and appending a stream in parallel style
203+-------------------------------------------------------------------------------
205+-- Note that consing and appending requires StreamK as it would not scale well
206+-- with StreamD unless we are only consing a very small number of streams or
207+-- elements in a stream. StreamK allows us to manipulate control flow in a way
208+-- which StreamD cannot allow. StreamK can make a jump without having to
209+-- remember the past state.
123{-# NOINLINE forkSVarPar #-}
211{-# NOINLINE forkSVarPar #-}
124forkSVarPar :: (IsStream t, MonadAsync m)
212forkSVarPar :: (IsStream t, MonadAsync m)
125 => SVarStopStyle -> t m a -> t m a -> t m a
213 => SVarStopStyle -> t m a -> t m a -> t m a
@@ -144,6 +232,10 @@ joinStreamVarPar style ss m1 m2 = mkStream $ \st yld sng stp ->
144 foldStreamShared st yld sng stp m2
232 foldStreamShared st yld sng stp m2
145 _ -> foldStreamShared st yld sng stp (forkSVarPar ss m1 m2)
233 _ -> foldStreamShared st yld sng stp (forkSVarPar ss m1 m2)
235+-------------------------------------------------------------------------------
237+-------------------------------------------------------------------------------
147-- | XXX we can implement it more efficienty by directly implementing instead
239-- | XXX we can implement it more efficienty by directly implementing instead
148-- of combining streams using parallel.
240-- of combining streams using parallel.
149{-# INLINE consMParallel #-}
241{-# INLINE consMParallel #-}
@@ -194,24 +286,57 @@ mkParallel m = do
194-- Stream to stream concurrent function application
286-- Stream to stream concurrent function application
195------------------------------------------------------------------------------
287------------------------------------------------------------------------------
197−{-# INLINE applyParallel #-}
289+{-# INLINE _applyParallelK #-}
198−applyParallel :: (IsStream t, MonadAsync m) => (t m a -> t m b) -> t m a -> t m b
290+_applyParallelK :: (IsStream t, MonadAsync m)
291+ => (t m a -> t m b) -> t m a -> t m b
199−applyParallel f m = mkStream $ \st yld sng stp -> do
292+_applyParallelK f m = mkStream $ \st yld sng stp -> do
200 sv <- newParallelVar StopNone (adaptState st)
293 sv <- newParallelVar StopNone (adaptState st)
201 pushWorkerPar sv (runOne st{streamVar = Just sv} (toStream m))
294 pushWorkerPar sv (runOne st{streamVar = Just sv} (toStream m))
202 foldStream st yld sng stp $ f $ fromSVar sv
295 foldStream st yld sng stp $ f $ fromSVar sv
297+-- XXX need to implement fromSVar in direct style for fused generation from
299+{-# INLINE applyParallelD #-}
300+applyParallelD :: (IsStream t, MonadAsync m)
301+ => (t m a -> t m b) -> t m a -> t m b
302+applyParallelD f m = mkStream $ \st yld sng stp -> do
303+ sv <- newParallelVar StopNone (adaptState st)
304+ pushWorkerParD (adaptState st) sv (D.toStreamD m)
305+ foldStream st yld sng stp $ f $ fromSVar sv
307+{-# INLINE applyParallel #-}
308+applyParallel :: (IsStream t, MonadAsync m)
309+ => (t m a -> t m b) -> t m a -> t m b
310+applyParallel = applyParallelD
204------------------------------------------------------------------------------
312------------------------------------------------------------------------------
205-- Stream runner concurrent function application
313-- Stream runner concurrent function application
206------------------------------------------------------------------------------
314------------------------------------------------------------------------------
208−{-# INLINE foldParallel #-}
316+{-# INLINE _foldParallelK #-}
209−foldParallel :: (IsStream t, MonadAsync m) => (t m a -> m b) -> t m a -> m b
317+_foldParallelK :: (IsStream t, MonadAsync m) => (t m a -> m b) -> t m a -> m b
210−foldParallel f m = do
318+_foldParallelK f m = do
211 sv <- newParallelVar StopNone defState
319 sv <- newParallelVar StopNone defState
212 pushWorkerPar sv (runOne defState{streamVar = Just sv} $ toStream m)
320 pushWorkerPar sv (runOne defState{streamVar = Just sv} $ toStream m)
213 f $ fromSVar sv
321 f $ fromSVar sv
323+-- XXX need to implement fromSVar in direct style for fused generation from
325+{-# INLINE foldParallelD #-}
326+foldParallelD :: (IsStream t, MonadAsync m) => (t m a -> m b) -> t m a -> m b
327+foldParallelD f m = do
328+ sv <- newParallelVar StopNone defState
329+ pushWorkerParD defState sv (D.toStreamD m)
332+{-# INLINE foldParallel #-}
333+foldParallel :: (IsStream t, MonadAsync m) => (t m a -> m b) -> t m a -> m b
334+foldParallel = foldParallelD
336+------------------------------------------------------------------------------
338+------------------------------------------------------------------------------
215{-# INLINE teeToSVar #-}
340{-# INLINE teeToSVar #-}
216teeToSVar :: (IsStream t, MonadAsync m) => SVar Stream m a -> t m a -> t m a
341teeToSVar :: (IsStream t, MonadAsync m) => SVar Stream m a -> t m a -> t m a
217teeToSVar svr m = mkStream $ \st yld sng stp -> do
342teeToSVar svr m = mkStream $ \st yld sng stp -> do