[GHC] #14941: Switching direct type family application to EqPred (~) prevents inlining in code using vector (10x slowdown)

GHC ghc-devs at haskell.org
Tue Sep 18 22:30:47 UTC 2018


#14941: Switching direct type family application to EqPred (~) prevents inlining in
code using vector (10x slowdown)
-------------------------------------+-------------------------------------
        Reporter:  nh2               |                Owner:  davide
            Type:  bug               |               Status:  new
        Priority:  normal            |            Milestone:
       Component:  Compiler          |              Version:  8.2.2
      Resolution:                    |             Keywords:
Operating System:  Unknown/Multiple  |         Architecture:
 Type of failure:  Runtime           |  Unknown/Multiple
  performance bug                    |            Test Case:
      Blocked By:                    |             Blocking:
 Related Tickets:                    |  Differential Rev(s):
       Wiki Page:                    |
-------------------------------------+-------------------------------------

Comment (by simonpj):

 Thanks for the simpler example.  I think I now understand what
 is going on.  It's all to do with strictness analysis.

 Consider this function, and suppose it is strict in x:
 {{{
 f1 :: Int -> blah
 f1 x = ...(case x of I# y -> ...)...
 }}}
 The strictness analysis sees that `f1` is strict in `x` and we get a w/w
 split
 {{{
 f1 :: Int -> blah
 f1 x = case x of I# y -> $wf y

 $wf1 :: Int# -> blah
 $wf1 y = let x = I# y in ...original body of f...
 }}}
 Now suppose that we have
 {{{
 type family F a
 type instance F Bool = Int

 f2 :: F Bool -> blah
 f2 x = ...same as before...
 }}}
 In fact the strictness analysis still sees that `f2` is strict in `x`,
 and worker wrapper works too -- all by using the family instances.
 We get this
 {{{
 f2 :: F Bool -> blah
 f2 x = case (x |> g1) of I# y -> $wf2 y

 $wf2 :: Int# -> blah
 $wf2 y = let x = (I# y) |> sym g
          in ..as before...
 }}}
 Here `g` is a coercion `g :: F Bool ~ Int`, constructed from the family
 instances.  This coersionn is generated by `topNormaliseType_maybe`,
 itself called from `deepSplitCprType_maybe` in `WwLib`, during
 worker/wrapper
 generation.

 But it's harder if the coercion is purely local. Let's start with this
 (yes I know you can't write `(~#)` in source Haskell but imagine this
 is Core:
 {{{
 f3 :: forall a. (a ~# Int) => a -> blah
 f3 a (g :: (a ~# Int) (x :: a) = ...as before...
 }}}
 What we'd like is this:
 {{{
 f3 :: forall a. (a ~# Int) => a -> blah
 f3 a (g :: (a ~# Int) (x :: a) = case (x |> g) of I# y -> $wf3 a g y

 $wf3 :: forall a. (a ~# Int) => Int# -> blah
 $wf3 a g y = let x = (I# y) |> sym g
             in ...as before...
 }}}
 but alas neither the demand analyser, nor the worker/wrapper generator
 are clever enough to do this.   We need a kind of `normaliseType` that
 can take some local "givens" as well as the top-level family instance
 envs.

 This has the same ring as something Ryan wanted in the coverage
 checker.

 It's not obvious exactly what "should work".  Eg what about `(Int ~# a)`,
 or even `([Int] ~# [a])`?

 Finally, there is the question of `(~)` vs `(~#)`.  I think we are very
 aggressive about unboxing `(~)` constraints, so I'd like this:
 {{{
 f4 :: forall a. (a ~ Int) => a -> blah
 f4 a (g :: (a ~ Int) (x :: a)
   = case g of MkTwiddle (g2 :: a ~# Int) ->
     case (x |> g2) of I# y -> $wf4 a g2 y

 $wf4 :: forall a. (a ~# Int) => Int# -> blah
 $wf4 a g2 y = let x = (I# y) |> sym g2
                   g = MkTwiddle g2
             in ...as before...
 }}}
 Making all this happen will take some work though.

 How important is it.  I'm tempted to say "don't write types like that"!
 (Although the type checker goes to some trouble to support them well.)

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


More information about the ghc-tickets mailing list