Am Samstag 17 April 2010 22:11:05 schrieb Bertram Felgenhauer: > Daniel Fischer wrote: > > Am Samstag 17 April 2010 14:41:28 schrieb Simon Peyton-Jones: > > > I have not been following the details of this, I'm afraid, but I > > > notice > > > > this: > > > > forever' m = do _ <- m > > > > forever' m > > > > > > When I define that version of forever, the space leak goes away. > > > > > > What was the old version of forever that led to the leak? > > > > Control.Monad.forever > > > > forever :: Monad m => m a -> m b > > forever m = m >> forever m > > > > However, that isn't the problem. In my tests, both variants of forever > > exhibit the same behaviour, what makes it leak or not is the > > optimisation level. > > This definition, plus sharing, is the source of the space leak. > Consider this modification of your code: > > import Control.Concurrent > > always :: Monad m => m a -> m b > always a = -- let act = a >> act in act > do > _ <- a > always a > > noop :: IO () > noop = return () > > body :: IO () > body = always noop > > spawner :: IO () > spawner = do > forkIO $ body > putStrLn "Delaying" > threadDelay 1000000 > body `seq` return () > > main :: IO () > main = do > putStrLn "Spawning" > forkIO spawner > putStrLn "Delaying main" > threadDelay 4000000 > > Note that the 'always' in 'spawner' is gone, but it still exhibits the > space leak. The leak goes away if the final line of 'spawner' is > removed, hinting at the real problem: 'always' actually creates a long > chain of actions instead of tying the knot.
Except that with optimisations turned on, GHC ties the knot for you (at least if always isn't exported). Without -fno-state-hack, the knot is tied so tightly that always (return ()) is never descheduled (and there's no leak). With -fno-state-hack, I get Rec { Main.main_always :: GHC.Types.IO () -> GHC.Types.IO () GblId [Arity 1 NoCafRefs Str: DmdType L] Main.main_always = \ (a_aeO :: GHC.Types.IO ()) -> let { k_sYz :: GHC.Types.IO () LclId [Str: DmdType] k_sYz = Main.main_always a_aeO } in (\ (eta_ann :: GHC.Prim.State# GHC.Prim.RealWorld) -> case (a_aeO `cast` (GHC.Types.NTCo:IO () :: GHC.Types.IO () ~ (GHC.Prim.State# GHC.Prim.RealWorld -> (# GHC.Prim.State# GHC.Prim.RealWorld, () #)))) eta_ann of _ { (# new_s_anz, _ #) -> (k_sYz `cast` (GHC.Types.NTCo:IO () :: GHC.Types.IO () ~ (GHC.Prim.State# GHC.Prim.RealWorld -> (# GHC.Prim.State# GHC.Prim.RealWorld, () #)))) new_s_anz }) `cast` (sym (GHC.Types.NTCo:IO ()) :: (GHC.Prim.State# GHC.Prim.RealWorld -> (# GHC.Prim.State# GHC.Prim.RealWorld, () #)) ~ GHC.Types.IO ()) end Rec } which, despite tying the knot, leaks (so the program at least terminates). > > Indeed the following definition of 'always' (or 'forever') fares better > in that regard, but is more susceptible to producing unproductive loops: Indeed, that doesn't terminate with -O2 -fno-state-hack > > always a = let act = a >> act in act > > (I used noop = yield for avoiding that problem in my tests) > > regards, > > Bertram _______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe