[GHC] #12425: With -O1 and above causes ghc to use all available memory before being killed by OOM killer

GHC ghc-devs at haskell.org
Thu Dec 1 23:25:18 UTC 2016


#12425: With -O1 and above causes ghc to use all available memory before being
killed by OOM killer
-------------------------------------+-------------------------------------
        Reporter:  erikd             |                Owner:
            Type:  bug               |               Status:  new
        Priority:  high              |            Milestone:  8.0.3
       Component:  Compiler          |              Version:  8.0.1
      Resolution:                    |             Keywords:  Inlining
Operating System:  Unknown/Multiple  |         Architecture:
 Type of failure:  Compile-time      |  Unknown/Multiple
  performance bug                    |            Test Case:
      Blocked By:                    |             Blocking:
 Related Tickets:                    |  Differential Rev(s):
       Wiki Page:                    |
-------------------------------------+-------------------------------------

Comment (by simonpj):

 Thomie, THANK YOU for the small repro case in comment:3.  With its help I
 rapidly nailed the bug.   Here's what is happening.

 Suppose we have
 {{{
 let rec { f = ...g...g...
         ; g = ...f...f... }
 in
 case x of
   True  -> ...f..
   False -> ..f...
 }}}
 In each iteration of the simplifier the occurrence analyser `OccAnal`
 chooses a loop breaker.  Suppose in iteration 1 it choose `g` as the loop
 breaker.  That means it is free to inline `f`.

 Suppose that GHC decides to inline `f` in the branches of the `case`, but
 (for some reason; eg it is not satureated) in the rhs of `g`. So we get
 {{{
 let rec { f = ...g...g...
         ; g = ...f...f... }
 in
 case x of
   True  -> ...g...g.....
   False -> ..g..g....
 }}}
 Now suppose that, for some reason, in the next iteraion the occurrence
 analyser chooses `f` as the loop breaker, so it can freely inling `g`.
 And again for some reason the simplifer inlines `g` at its calls in the
 `case` branches, but not in the RHS of `f`. Then we get
 {{{
 let rec { f = ...g...g...
         ; g = ...f...f... }
 in
 case x of
   True  -> ...(...f...f...)...(...f..f..).....
   False -> ..(...f...f...)...(..f..f...)....
 }}}
 You can see where this is going! Each iteration of the simplifier doubles
 the number of calls to `f` or `g`.  No wonder GHC is slow!

 (In the particular example in comment:3, `f` and `g` are the two mutually
 recursive `fmap` instances for `CondT` and `Result`.  They are both marked
 INLINE which, oddly, is why they don't inline in each other's RHS, because
 the call there is not saturated.)

 The root cause is that we flip-flop on our choice of loop breaker. I
 always thought it didn't matter, and indeed for any single iteration to
 terminate, it doesn't matter.  But when we iterate, it matters a lot!!

 I think this was not happening before because, by a fluke, we tended to
 always choose the same one.  But, perhaps as a resul of Bartosz's work on
 making GHC deterministic, the occurrence analyser now reliably flip-flops
 in each iteration, as above.

 Trac #12234 turns out to be ''exactly'' the same issue

 -------------------

 What to do?  We want the occurrence analyser to choose a loop breaker
 based on stable criteria, not arbitrary things.  Simple idea: if two or
 more potential loop breakers look equally plausible, ''choose the one that
 was a loop breaker in the previous iteration''.  That should make it
 stable.

 Stay tuned.  But I thought I'd explain what is happening in case I get
 distracted.

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


More information about the ghc-tickets mailing list