[GHC] #14610: newtype wrapping of a monadic stack kills performance

GHC ghc-devs at haskell.org
Tue Dec 26 05:29:35 UTC 2017


#14610: newtype wrapping of a monadic stack kills performance
-------------------------------------+-------------------------------------
        Reporter:  mrkkrp            |                Owner:  (none)
            Type:  bug               |               Status:  new
        Priority:  normal            |            Milestone:  8.6.1
       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 dfeuer):

 Here's an example that doesn't quite go far enough to demonstrate the
 problem, but seems closer in spirit to the original.

 {{{#!hs
 newtype D a = D {getD :: a}
 d :: a -> D a
 d a = D a

 baz :: D Int -> Int -> D Int
 baz y x0 = foo x0
   where
     foo :: Int -> D Int
     foo 0 = y
     foo x = D (bar (x - 3))

     bar :: Int -> Int
     bar 0 = getD y
     bar x = getD (foo x)
 }}}

 Compiling with `-dverbose-core2core`, we see that after the first
 simplifier run (gentle, before floating), we get

 {{{
 baz :: D Int -> Int -> D Int
 [LclIdX,
  Arity=2,
  Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
          WorkFree=True, Expandable=True, Guidance=IF_ARGS [0 0] 121 0}]
 baz
   = \ (y_aSE :: D Int) (x0_aSF :: Int) ->
       letrec {
         foo_aSG [Occ=LoopBreaker] :: Int -> D Int
         [LclId,
          Arity=1,
          Unf=Unf{Src=<vanilla>, TopLvl=False, Value=True, ConLike=True,
                  WorkFree=True, Expandable=True, Guidance=IF_ARGS [20] 81
 0}]
         foo_aSG
           = \ (ds_d27R :: Int) ->
               case ds_d27R of { GHC.Types.I# ds_d27T ->
               case ds_d27T of ds_X289 {
                 __DEFAULT ->
                   (case ds_X289 of ds_X284 {
                      __DEFAULT ->
                        (foo_aSG (GHC.Types.I# (GHC.Prim.-# ds_X284 3#)))
                        `cast` (Foo.N:D[0] <Int>_R :: (D Int :: *) ~R# (Int
 :: *));
                      3# ->
                        y_aSE `cast` (Foo.N:D[0] <Int>_R :: (D Int :: *)
 ~R# (Int :: *))
                    })
                   `cast` (Sym (Foo.N:D[0] <Int>_R) :: (Int :: *) ~R# (D
 Int :: *));
                 0# -> y_aSE
               }
               }; } in
       foo_aSG x0_aSF
 }}}

 Note that `foo_aSG` is bound by `letrec`.

 If we switch to a type synonym version,

 {{{#!hs
 type D a = a
 getD :: D a -> a
 getD a = a

 d :: a -> D a
 d a = a

 baz :: Int -> Int -> Int
 baz y x0 = foo x0
   where
     foo :: Int -> Int
     foo 0 = y
     foo x = d (bar (x - 3))

     bar :: Int -> Int
     bar 0 = getD y
     bar x = getD (foo x)
 }}}

 then at the same point in core2core we instead see

 {{{#!hs
 baz :: Int -> Int -> Int
 [LclIdX,
  Arity=2,
  Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
          WorkFree=True, Expandable=True, Guidance=IF_ARGS [0 0] 59 0}]
 baz
   = \ (y_aSQ :: Int) (x0_aSR :: Int) ->
       joinrec {
         foo_aSS [Occ=LoopBreaker] :: Int -> Int
         [LclId[JoinId(1)],
          Arity=1,
          Unf=Unf{Src=<vanilla>, TopLvl=False, Value=True, ConLike=True,
                  WorkFree=True, Expandable=True, Guidance=IF_ARGS [20] 55
 0}]
         foo_aSS (ds_d27X :: Int)
           = case ds_d27X of { GHC.Types.I# ds_d27Z ->
             case ds_d27Z of ds_X28a {
               __DEFAULT -> jump foo_aSS (GHC.Types.I# (GHC.Prim.-# ds_X28a
 3#));
               0# -> y_aSQ;
               3# -> y_aSQ
             }
             }; } in
       jump foo_aSS x0_aSR
 }}}

 The reason this example doesn't quite go far enough is that later
 transformations work out the kinks and recognize the join point. But based
 on the bug report, that isn't always the case.

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


More information about the ghc-tickets mailing list