[GHC] #8032: Worker-wrapper transform triggers bad reboxing behavior

GHC ghc-devs at haskell.org
Wed Jul 3 02:31:45 CEST 2013


#8032: Worker-wrapper transform triggers bad reboxing behavior
------------------------------------+---------------------------------------
Reporter:  ezyang                   |          Owner:                  
    Type:  bug                      |         Status:  new             
Priority:  normal                   |      Component:  Compiler        
 Version:  7.7                      |       Keywords:                  
      Os:  Unknown/Multiple         |   Architecture:  Unknown/Multiple
 Failure:  Runtime performance bug  |      Blockedby:                  
Blocking:                           |        Related:                  
------------------------------------+---------------------------------------
 When we worker-wrapper transform functions, we tend to be to eager to
 unbox all of our arguments and pass them to the worker. This backfires
 when the boxed version of the argument is needed:

 {{{
 module Gnam where

 data KST = KST {
     ke :: {-# UNPACK #-} !Int
   , ka :: {-# UNPACK #-} !Int
   }

 data KStop r = KNeedInput Int !(KC r)
 data Result r = Result r
 type KC r = KST -> Int -> Result r

 newtype K r a = K {
     unK :: (KST -> KStop r -> Result r) -> KC r
   }

 skipWhileK :: K () ()
 skipWhileK =
   K $ \kf kst at KST{ ke = e } i0 ->
   let loop i rw0 -- Note: removing rw0 argument gets rid of re-boxing
 behavior
         | i < e = loop (i + 1) rw0
         | otherwise = unK recurse kf kst i
   in loop i0 (0 :: Int)
   where -- Note that without NOINLINE, we get an unnecessary eager
         -- closure allocation even when closure is never used.  This
         -- is unfortunate because recurse is the slow path (e.g.,
         -- called 1/4000 of the time in the real code).
         {-# NOINLINE recurse #-}
         recurse = K $ \kf kst i -> kf kst $ KNeedInput i $ unK skipWhileK
 kf
 }}}

 skipWhileK is an important loop which we would like to avoid performing
 heap allocation on. However, this code is compiled by HEAD with an
 allocation which reboxes KST:

 {{{
 Gnam.$wa
   :: (Gnam.KST -> Gnam.KStop () -> Gnam.Result ())
      -> GHC.Prim.Int#
      -> GHC.Prim.Int#
      -> GHC.Prim.Int#
      -> Gnam.Result ()
 [GblId,
  Arity=4,
  Caf=NoCafRefs,
  Str=DmdType <C(C(S)),U><L,U><L,U><L,U>,
  Unf=OtherCon []] =
     \r srt:SRT:[] [w_srG ww_srz ww1_srA ww2_srK]
         let {
           wild_srB :: Gnam.KST
           [LclId, Str=DmdType, Unf=OtherCon []] =
               NO_CCS Gnam.KST! [ww_srz ww1_srA];
         } in ...
 }}}

 The worker function takes i and the unboxed KST as arguments, as one might
 hope, but when it discovers that it needs KST on the inside, it has no
 choice but to reconstruct KST inside. What should be done instead is
 wild_srB (produced by the case analysis on KST here:

 {{{
     \r srt:SRT:[] [w_srV w1_srO w2_srS]
         case w1_srO of _ { // <--- wild_ here please!
           Gnam.KST ww1_srW ww2_srX ->
               case w2_srS of _ {
                 GHC.Types.I# ww4_srY -> Gnam.$wa w_srV ww1_srW ww2_srX
 ww4_srY;
               };
         };
 }}}

 This is perhaps a tradeoff: passing the evaluated version of things that
 were unpacked costs an extra argument; however, unboxing things can result
 in a lot of extra arguments! (And GHC doesn't seem to eliminate unused
 arguments, even when the rebox is not necessary.)

-- 
Ticket URL: <http://hackage.haskell.org/trac/ghc/ticket/8032>
GHC <http://www.haskell.org/ghc/>
The Glasgow Haskell Compiler



More information about the ghc-tickets mailing list