[GHC] #9520: Running an action twice uses much more memory than running it once

GHC ghc-devs at haskell.org
Fri Aug 19 08:48:22 UTC 2016


#9520: Running an action twice uses much more memory than running it once
-------------------------------------+-------------------------------------
        Reporter:  snoyberg          |                Owner:
            Type:  bug               |               Status:  new
        Priority:  normal            |            Milestone:
       Component:  Compiler          |              Version:  7.8.3
      Resolution:                    |             Keywords:
Operating System:  Linux             |         Architecture:  x86_64
 Type of failure:  Runtime           |  (amd64)
  performance bug                    |            Test Case:
      Blocked By:                    |             Blocking:
 Related Tickets:                    |  Differential Rev(s):
       Wiki Page:                    |
-------------------------------------+-------------------------------------

Comment (by edsko):

 I have no answers here, just more questions. I ran into this problem again
 with a large project that uses conduit. My program suffered from a large
 memory leak, and in the `-hy` profile the types were reported as `->Pipe`
 and `Sink`; moreover, the `-hc` profile told me memory was being retained
 by a CAF. All of this pointed to the exact problem discussed in this
 ticket, and indeed adding

 {{{#!hs
 {-# OPTIONS_GHC -fno-full-laziness #-}
 }}}

 to the top of my module got rid of the problem. However, I can't say I
 fully understand what is going on. Experimenting with @snoyberg 's
 examples, above, I noticed that the memory behaviour of these modules
 interacts in odd ways with profiling options, which doesn't make this any
 easier! For @snoyberg's first example
 (https://ghc.haskell.org/trac/ghc/ticket/9520#comment:4):

 {{{
     | No profiling | -prof   | -prof -fprof-auto
 ----+--------------+---------+------------------
 -O0 | OK           | OK      | OK
 -O1 | OK           | OK      | LEAK(1)
 -O2 | OK           | OK      | LEAK(1)
 }}}

 where OK means "runs in constant space" and LEAK(1) indicates a memory
 leak consisting of `Int`, `->Sink` and `Sink`, according to `+RTS -hy`. In
 other words, this has a memory leak ''only'' when ''both'' optimization
 ''and'' `-fprof-auto` are specified (`-fprof` by itself is not enough).

 Bizarrely, for the second example the behaviour is reversed (perhaps this
 is why Michael concluded that this example "however, does '''not'''
 demonstrate the problem"?):

 {{{
     | No profiling | -prof   | -prof -fprof-auto
 ----+--------------+---------+------------------
 -O0 | OK           | OK      | OK
 -O1 | LEAK         | LEAK(1) | OK
 -O2 | LEAK         | LEAK(1) | OK
 }}}

 Unlike for the first example, here we also get a LEAK without any
 profiling enabled (as indicated by a very high maximum residency reported
 by `+RTS -s`).

 I added a third example:

 {{{#!hs
 foreign import ccall "doNothing" doNothing :: IO ()

 data Sink i r = Sink (i -> Sink i r) r

 sinkCount :: Sink i Int
 sinkCount =
     loop 0
   where
     loop cnt = Sink (\_ -> loop $! cnt + 1) cnt

 feed :: Sink Char r -> IO r
 feed =
     loop 10000000
   where
     loop 0 (Sink _ g) = return g
     loop i (Sink f _) = doNothing >> loop (i - 1) (f 'A')

 action :: IO ()
 action = do
     feed sinkCount
     return ()

 main :: IO ()
 main = do
     action
     action
 }}}

 This differs from @snoyberg 's second example only in the additional call
 to `doNothing` in `feed.loop`; `doNothing` is defined in an external `.c`
 file:

 {{{#!c
 void doNothing() {}
 }}}

 (I used an externally defined C function because I wanted something that
 the optimizer couldn't get rid of but without getting all kinds of crud
 about `Handle`s etc in the core/STG output, which is what would happen
 with a print statement, say.) I have no idea why, but this program's
 memory behaviour is quite different from version 2:

 {{{
     | No profiling | -prof   | -prof -fprof-auto
 ----+--------------+---------+------------------
 -O0 | LEAK         | LEAK(2) | LEAK(2)
 -O1 | LEAK         | LEAK(1) | LEAK(1)
 -O2 | LEAK         | LEAK(1) | LEAK(1)
 }}}

 Now this program leaks no matter what we do; although LEAK(2) reported
 here, according to `RTS -hy`, consists of different type (a single type,
 in fact: `PAP`).

 Getting to the bottom of this would require more time than I currently
 have; I guess for me the take-away currently is: full laziness is
 dangerous when using free monads such as conduit.

--
Ticket URL: <http://ghc.haskell.org/trac/ghc/ticket/9520#comment:9>
GHC <http://www.haskell.org/ghc/>
The Glasgow Haskell Compiler


More information about the ghc-tickets mailing list