[GHC] #13207: Performance regressions from removing LNE analysis
GHC
ghc-devs at haskell.org
Mon Jan 30 05:48:02 UTC 2017
#13207: Performance regressions from removing LNE analysis
-------------------------------------+-------------------------------------
Reporter: lukemaurer | Owner: lukemaurer
Type: bug | Status: new
Priority: normal | Milestone:
Component: Compiler | Version: 8.0.1
Keywords: JoinPoints | Operating System: Unknown/Multiple
Architecture: | Type of failure: None/Unknown
Unknown/Multiple |
Test Case: | Blocked By:
Blocking: | Related Tickets: 12988
Differential Rev(s): | Wiki Page:
-------------------------------------+-------------------------------------
`CoreToStg`'s LNE analysis has been superseded by the occurrence
analyzer's join-point analysis. However, there are a few cases where the
occurrence analyzer doesn't find a join point where the old analysis
would've found an LNE. Known causes include:
1. The LNE analysis was untyped, which (very) occasionally allows an LNE
where a join point would've fallen afoul of the polymorphism rule (see
Note [The polymorphism rule of join points] in `CoreSyn.hs`).
2. Sometimes a rewrite rule prevents a binding from being christened a
join point; such a binding would've been found by the LNE analysis (which
runs after Core Tidy has removed rules from local binders).
The biggest loss in NoFib is `spectral/cryptarithm2`, which saw a 0.3%
increase in allocations because a rule prevented detection of a join
point. Specifically, there's a `go` that gets SpecConstr'd as an `$sgo`.
All the calls to `$sgo` happen to be tail calls, but some calls to `go`
aren't, so it's only the rule that keeps `$sgo` from becoming a join
point.
In summary (adapted from the `cryptarithm2` situation):
{{{
letrec $sgo = \x y z -> ... go {- non-tail call -} ...
{-# RULE "SPEC go" forall x y z. go ((x,y):z) = $sgo x y z #-}
go = \xs -> ... go ...
in ... go ... $sgo a b c {- tail call -} ...
}}}
The only thing keeping `$sgo` from becoming a join point is the rule.
Possible solutions:
1. Run the occurrence analyzer after Core Tidy. Seems like the solution
needing the least code. Problems:
* Core Tidy turns top-level binders into `GlobalId`s. Would need to
adapt the occurrence analyzer.
* Would also need to run the simplifier, or at least `simpleOptPgm`,
after Core Tidy, so one of those would also have to adapt.
* Only covers case 2.
2. Write a new pass that just throws out all the rules from the local
binders in the module, then run it (and then the simplifier) at the end of
the Core-to-Core pipeline, just before Core Tidy. (Local rules don't
appear in ifaces anyway.)
* Needs a whole new traversal, albeit a very simple one. Though it's
possible that dropping the rules would lead to other optimizations (`$sgo`
would end up pre-inlined in the above example), so it might be helpful
anyway.
* Also only covers case 2.
3. Write the LNE analysis as an STG-to-STG pass. Only idea that covers
both cases.
* Needs even more code, and duplicative of the occurrence analyzer.
4. Loosen the join-point invariants somehow. For example, if we could flag
a rule as applying only to tail calls, that rule could contain a jump in
its RHS even though its LHS isn't a jump.
* But this is already problematic in the above example—we also can't
make `$sgo` a join point so long as it's mutually recursive with `go`,
which it still is so long as the rule is there. In other words, even if
the rule doesn't directly keep `$sgo` from being a join point, it's still
the only thing keeping it from breaking free of `go`'s recursive group,
which it must do to become a join point.
* So it doesn't cover case 2 very well and it doesn't cover case 1 at
all.
--
Ticket URL: <http://ghc.haskell.org/trac/ghc/ticket/13207>
GHC <http://www.haskell.org/ghc/>
The Glasgow Haskell Compiler
More information about the ghc-tickets
mailing list