[GHC] #9476: Implement late lambda-lifting

GHC ghc-devs at haskell.org
Fri Sep 7 09:08:55 UTC 2018


#9476: Implement late lambda-lifting
-------------------------------------+-------------------------------------
        Reporter:  simonpj           |                Owner:  sgraf
            Type:  feature request   |               Status:  new
        Priority:  normal            |            Milestone:
       Component:  Compiler          |              Version:  7.8.2
      Resolution:                    |             Keywords:  LateLamLift
Operating System:  Unknown/Multiple  |         Architecture:
 Type of failure:  Runtime           |  Unknown/Multiple
  performance bug                    |            Test Case:
      Blocked By:                    |             Blocking:
 Related Tickets:  #8763 #13286      |  Differential Rev(s):
       Wiki Page:  LateLamLift       |
-------------------------------------+-------------------------------------

Comment (by sgraf):

 Replying to [comment:28 simonpj]:
 > > -fstg-lift-lams-known: Allow turning known into unknown calls.
 >
 > In your example, you say that we might float `f` but not `g`.  But why
 don't we float `g`?

 Because at the time we handled `g`, it (hypothetically) was deemed non-
 beneficial to lift. It might lead to more allocation or hurt some other
 heuristic.

 Of course one could argue that lifting ''both'' bindings could be a win.
 Our decision strategy is greedy in that regard; There's no backtracking
 involved. We transform expressions going from outer to inner, at each step
 taking into account the lifting decisions we already made (and which we
 don't question or undo). So, for that example in particular, the
 assumption was that we examined `g`, decided not to lift it and then look
 at `f` knowing that `g` won't be lifted.

 > > lifting go to top-level will result in abstracting over `$warg`,
 >
 > Why didn't we float `$warg`?

 Same reasoning. Because we decided earlier that the lift might not be
 worthwhile. I could look at some debug output to see what was the
 reason(s) for `$warg` if you are really interested.

 Replying to [comment:29 simonpj]:
 > > Ah, but one of them (the last, in particular) is a 'void' parameter
 (so, slow call pattern pppppv), which is completely erased from the
 resulting Cmm!
 >
 > OK, so the conclusion is: don't count void args when counting args and
 comparing with the threshold?  I agree!

 Yes, exactly. See
 https://github.com/sgraf812/ghc/blob/9b9260c1d45d127edf9ebdfe04a3daaff24a9dea/compiler/simplStg/StgLiftLams/Analysis.hs#L310

 Replying to [comment:30 simonpj]:
 > The non-recursive case
 >
 > So it's a bit surprising to me that even with a threshold of 10 we have
 > such a small hit on instruction count.

 I was wondering the same thing. I ''think'' the other heuristics
 (https://github.com/sgraf812/ghc/blob/9b9260c1d45d127edf9ebdfe04a3daaff24a9dea/compiler/simplStg/StgLiftLams/Analysis.hs#L228-L235,
 for completeness) fire way too often to allow non-recursive calls with 20
 arguments. Consider the impact on allocations: When we lift such a
 function, all 20ish free variables would have to be made available at all
 call sites, leading to a massive increase in closure allocation for
 closures around the actual call sites. Example

 {{{
 let f = [x1...x20] \[a b c] -> ...
     g = [f y] \z -> f y y z
 in map g [1,2,3]
 }}}

 Assuming for the sake of the example that `g` wouldn't be lifted to top-
 level itself (it's just a PAP of `f`), lifting `f` would just shift the
 whole closure into `g`'s closure. In general, there could be arbitrary
 more closures between `f`s binding site and its call sites, each of which
 would have to carry `f`s former free variables, should we decide to lift
 it.

 I could try and measure with the allocation heuristic and the max args
 heuristic turned off, to see if we get more chaotic results.

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


More information about the ghc-tickets mailing list