@@ -37,7 +37,7 @@ module Streamly.SVar
37 , atomicModifyIORefCAS
37 , atomicModifyIORefCAS
38 , ChildEvent (..)
38 , ChildEvent (..)
39 , AheadHeapEntry (..)
39 , AheadHeapEntry (..)
40− , send
40+ , sendYield
41 , sendStop
41 , sendStop
42 , enqueueLIFO
42 , enqueueLIFO
43 , workLoopLIFO
43 , workLoopLIFO
@@ -46,7 +46,6 @@ module Streamly.SVar
46 , enqueueAhead
46 , enqueueAhead
47 , pushWorkerPar
47 , pushWorkerPar
50 , queueEmptyAhead
49 , queueEmptyAhead
51 , dequeueAhead
50 , dequeueAhead
52 , dequeueFromHeap
51 , dequeueFromHeap
@@ -158,6 +157,7 @@ data SVar t m a =
159 -- Shared output queue (events, length)
158 -- Shared output queue (events, length)
160 , outputQueue :: IORef ([ChildEvent a], Int)
159 , outputQueue :: IORef ([ChildEvent a], Int)
160+ , maxYieldLimit :: Maybe (IORef Int)
161 , outputDoorBell :: MVar () -- signal the consumer about output
161 , outputDoorBell :: MVar () -- signal the consumer about output
162 , readOutputQ :: m [ChildEvent a]
162 , readOutputQ :: m [ChildEvent a]
163 , postProcess :: m Bool
163 , postProcess :: m Bool
@@ -187,9 +187,10 @@ data SVar t m a =
189data State t m a = State
189data State t m a = State
190− { streamVar :: Maybe (SVar t m a)
190+ { streamVar :: Maybe (SVar t m a)
191+ , yieldLimit :: Maybe Int
191 , threadsHigh :: Int
192 , threadsHigh :: Int
192− , bufferHigh :: Int
193+ , bufferHigh :: Int
195defaultMaxThreads, defaultMaxBuffer :: Int
196defaultMaxThreads, defaultMaxBuffer :: Int
@@ -199,15 +200,23 @@ defaultMaxBuffer = 1500
199defState :: State t m a
200defState :: State t m a
200defState = State
201defState = State
201 { streamVar = Nothing
202 { streamVar = Nothing
203+ , yieldLimit = Nothing
202 , threadsHigh = defaultMaxThreads
204 , threadsHigh = defaultMaxThreads
203 , bufferHigh = defaultMaxBuffer
205 , bufferHigh = defaultMaxBuffer
208+-- XXX if perf gets affected we can have all the Nothing params in a single
209+-- structure so that we reset is fast. We can also use rewrite rules such that
210+-- reset occurs only in concurrent streams to reduce the impact on serial
206-- We can optimize this so that we clear it only if it is a Just value, it
212-- We can optimize this so that we clear it only if it is a Just value, it
207-- results in slightly better perf for zip/zipM but the performance of scan
213-- results in slightly better perf for zip/zipM but the performance of scan
208-- worsens a lot, it does not fuse.
214-- worsens a lot, it does not fuse.
209rstState :: State t m a -> State t m b
215rstState :: State t m a -> State t m b
210−rstState st = st {streamVar = Nothing}
217+ { streamVar = Nothing
218+ , yieldLimit = Nothing
212#ifdef DIAGNOSTICS
221#ifdef DIAGNOSTICS
213{-# NOINLINE dumpSVar #-}
222{-# NOINLINE dumpSVar #-}
@@ -337,7 +346,6 @@ doFork action exHandler =
338-- | This function is used by the producer threads to queue output for the
347-- | This function is used by the producer threads to queue output for the
339-- consumer thread to consume. Returns whether the queue has more space.
348-- consumer thread to consume. Returns whether the queue has more space.
340−{-# NOINLINE send #-}
341send :: Int -> SVar t m a -> ChildEvent a -> IO Bool
349send :: Int -> SVar t m a -> ChildEvent a -> IO Bool
342send maxOutputQLen sv msg = do
350send maxOutputQLen sv msg = do
343 len <- atomicModifyIORefCAS (outputQueue sv) $ \(es, n) ->
351 len <- atomicModifyIORefCAS (outputQueue sv) $ \(es, n) ->
@@ -355,6 +363,15 @@ send maxOutputQLen sv msg = do
355 void $ tryPutMVar (outputDoorBell sv) ()
363 void $ tryPutMVar (outputDoorBell sv) ()
356 return (len < maxOutputQLen || maxOutputQLen < 0)
364 return (len < maxOutputQLen || maxOutputQLen < 0)
366+{-# NOINLINE sendYield #-}
367+sendYield :: Int -> SVar t m a -> ChildEvent a -> IO Bool
368+sendYield maxOutputQLen sv msg = do
369+ ylimit <- case maxYieldLimit sv of
370+ Nothing -> return True
371+ Just ref -> atomicModifyIORefCAS ref $ \x -> (x - 1, x > 1)
372+ r <- send maxOutputQLen sv msg
373+ return $ r && ylimit
358{-# NOINLINE sendStop #-}
375{-# NOINLINE sendStop #-}
359sendStop :: SVar t m a -> IO ()
376sendStop :: SVar t m a -> IO ()
360sendStop sv = do
377sendStop sv = do
@@ -534,9 +551,6 @@ enqueueAhead sv q m = do
535-- XXX review for livelock
552-- XXX review for livelock
540{-# INLINE queueEmptyAhead #-}
554{-# INLINE queueEmptyAhead #-}
541queueEmptyAhead :: MonadIO m => IORef ([t m a], Int) -> m Bool
555queueEmptyAhead :: MonadIO m => IORef ([t m a], Int) -> m Bool
542queueEmptyAhead q = liftIO $ do
556queueEmptyAhead q = liftIO $ do
@@ -675,7 +689,15 @@ dispatchWorker maxWorkerLimit sv = do
675 -- executing. In that case we should either configure the maxWorker
689 -- executing. In that case we should either configure the maxWorker
676 -- count to higher or use parallel style instead of ahead or async
690 -- count to higher or use parallel style instead of ahead or async
677 -- style.
691 -- style.
692+ limit <- case maxYieldLimit sv of
693+ Nothing -> return maxWorkerLimit
695+ lim <- liftIO $ readIORef x
697+ if maxWorkerLimit > 0
698+ then min maxWorkerLimit lim
678− when (cnt < maxWorkerLimit || maxWorkerLimit < 0) $ pushWorker sv
700+ when (cnt < limit || limit < 0) $ pushWorker sv
680{-# NOINLINE sendWorkerWait #-}
702{-# NOINLINE sendWorkerWait #-}
681sendWorkerWait :: MonadAsync m => Int -> SVar t m a -> m ()
703sendWorkerWait :: MonadAsync m => Int -> SVar t m a -> m ()
@@ -816,6 +838,9 @@ getAheadSVar st f = do
816 wfw <- newIORef False
838 wfw <- newIORef False
817 running <- newIORef S.empty
839 running <- newIORef S.empty
818 q <- newIORef ([], -1)
840 q <- newIORef ([], -1)
841+ yl <- case yieldLimit st of
842+ Nothing -> return Nothing
843+ Just x -> Just <$> newIORef x
820#ifdef DIAGNOSTICS
845#ifdef DIAGNOSTICS
821 disp <- newIORef 0
846 disp <- newIORef 0
@@ -826,6 +851,7 @@ getAheadSVar st f = do
828 SVar { outputQueue = outQ
853 SVar { outputQueue = outQ
854+ , maxYieldLimit = yl
829 , outputDoorBell = outQMv
855 , outputDoorBell = outQMv
830 , readOutputQ = readOutputQBounded (threadsHigh st) sv
856 , readOutputQ = readOutputQBounded (threadsHigh st) sv
831 , postProcess = postProcessBounded sv
857 , postProcess = postProcessBounded sv
@@ -879,6 +905,7 @@ getParallelSVar = do
881 SVar { outputQueue = outQ
907 SVar { outputQueue = outQ
908+ , maxYieldLimit = Nothing
882 , outputDoorBell = outQMv
909 , outputDoorBell = outQMv
883 , readOutputQ = readOutputQPar sv
910 , readOutputQ = readOutputQPar sv
884 , postProcess = allThreadsDone sv
911 , postProcess = allThreadsDone sv