[GHC] #13253: Exponential compilation time with RWST & ReaderT stack with `-02`
GHC
ghc-devs at haskell.org
Tue Oct 16 13:19:50 UTC 2018
#13253: Exponential compilation time with RWST & ReaderT stack with `-02`
-------------------------------------+-------------------------------------
Reporter: phadej | Owner: bgamari, osa1
Type: bug | Status: new
Priority: normal | Milestone: 8.8.1
Component: Compiler | Version: 8.0.1
Resolution: | Keywords:
Operating System: Unknown/Multiple | Architecture:
Type of failure: Compile-time | Unknown/Multiple
performance bug | Test Case:
Blocked By: | Blocking:
Related Tickets: #15630 | Differential Rev(s):
Wiki Page: |
-------------------------------------+-------------------------------------
Comment (by tdammers):
I've instrumented GHC to dump more intermediate information during
SpecConstr. Specifically, I've added the following logic:
In `scExpr`, capture the expression to be specialized before and after
specialization; then compare number of terms to detect a blowup (a factor
of 2 is enough to weed out the non-blowups), and if this particular
incantation does in fact blow up, dump the expression before the blowup.
The resulting dump is a bit too large to attach, but it shows a typical
pattern: the expressions that blow up all look very much alike; as we
progress through the compilation, the "before" size stays at ~6000 terms,
while the "after" size progressively increases, and it looks like
expressions from earlier incantations get inlined into expressions later
in the process.
Which fits the hypothesis from the GHC HQ meeting, namely, that the
inlining heuristic only looks at the size of the inlinee, but not at the
inlining context, so when something gets inlined "top-down", then
functions to be inlined (which don't have their own dependencies inlined
yet) are all still small, and so the inliner happily copies them many
times, and then in the next round, it inlines exponentially more
invocations of the inlined functions' dependencies, and so on.
For example, given:
{{{#!haskell
f = g1 + g2 + g3 + g4 + g5
g1 = a1 + a2 + a3 + a4
g2 = b1 + b2 + b3 + b4
...
}}}
...the inlining heuristic will first look at `f`, conclude that each of
`g1` through `g5` is small, and can thus be inlined; then after inlining,
it will look at f again, and conclude that each of `a1` etc. is small, and
inline those; and if those have further dependencies following the same
matter, it will happily keep inlining all those small things, not
realizing that it is creating a monstrosity. And because all the inlinees
involved are different, there will not be any opportunities for
optimizations that might shrink things back down, so the resulting program
just keeps growing exponentially.
One fun thing I'll try now is this:
Considering that I already have code in place to detect blowups, I can
trivially use this logic to just say "if this blows up, then throw away
the specialized Core, and use the original expression instead". So I'll
try that, and see what that does to various performance metrics.
--
Ticket URL: <http://ghc.haskell.org/trac/ghc/ticket/13253#comment:41>
GHC <http://www.haskell.org/ghc/>
The Glasgow Haskell Compiler
More information about the ghc-tickets
mailing list