[GHC] #13966: Skip-less stream fusion (was: Missed optimization - loop fusion)

GHC ghc-devs at haskell.org
Fri Jul 14 18:08:56 UTC 2017


#13966: Skip-less stream fusion
-------------------------------------+-------------------------------------
        Reporter:  jmspiewak         |                Owner:  (none)
            Type:  feature request   |               Status:  new
        Priority:  normal            |            Milestone:
       Component:  Compiler          |              Version:  8.2.1-rc3
      Resolution:                    |             Keywords:  JoinPoints
Operating System:  Unknown/Multiple  |         Architecture:
                                     |  Unknown/Multiple
 Type of failure:  None/Unknown      |            Test Case:
      Blocked By:                    |             Blocking:
 Related Tickets:                    |  Differential Rev(s):
       Wiki Page:                    |
-------------------------------------+-------------------------------------
Changes (by jmspiewak):

 * priority:  low => normal
 * keywords:   => JoinPoints


Old description:

> I think GHC should be able to optimize func1 into func2, but currently it
> doesn't.
>
> {{{#!hs
> data Step s a = Done | Yield s a
>
> func1 high = loop1 0# 1# where
>   loop1 acc i = case loop2 i of
>     Done -> acc
>     Yield (I# i') (I# x) -> loop1 (acc +# x) i'
>
>   loop2 i = case tagToEnum# (i ># high) :: Bool of
>     False -> case remInt# i 2# of
>       0# -> Yield (I# (i +# 1#)) (I# i)
>       _ -> loop2 (i +# 1#)
>     True -> Done
>
> func2 high = loop1 0# 1# where
>   loop1 acc i = case tagToEnum# (i ># high) :: Bool of
>     False -> case remInt# i 2# of
>       0# -> loop1 (acc +# i) (i +# 1#)
>       _ -> loop1 acc (i +# 1#)
>     True -> acc
> }}}
>
> When using the LLVM backend func2 is almost 4x faster than func1.

New description:

 A simple stream chain
 {{{#!hs
 chain :: Int -> Int
 chain = sum . filter even . enumFromTo 1
 }}}
 doesn't fuse under a Skip-less stream on GHC 8.2-rc3 -O2.

 Benchmarked against a Skip stream (LLVM backend):
 {{{
 benchmarking Skip-less
 time                 248.9 ms   (243.3 ms .. 257.3 ms)
                      0.998 R²   (0.995 R² .. 0.999 R²)
 mean                 250.9 ms   (248.1 ms .. 254.7 ms)
 std dev              5.985 ms   (4.831 ms .. 7.311 ms)

 benchmarking Skip
 time                 61.26 ms   (60.39 ms .. 62.44 ms)
                      0.998 R²   (0.997 R² .. 0.999 R²)
 mean                 62.45 ms   (61.96 ms .. 62.91 ms)
 std dev              1.387 ms   (1.190 ms .. 1.669 ms)
 }}}


 Relevant core (chain1 is Skip-less, chain2 has Skip):
 {{{
 -- RHS size: {terms: 51, types: 27, coercions: 0, joins: 1/2}
 Main.$wchain1 [InlPrag=NOINLINE] :: Int# -> Int#
 [GblId, Arity=1, Caf=NoCafRefs, Str=<S,U>]
 Main.$wchain1
   = \ (ww_s9ep :: Int#) ->
       letrec {
         $wloop_s9ea [InlPrag=[0], Occ=LoopBreaker] :: Int# -> Step1 Int
 Int
         [LclId, Arity=1, Str=<S,U>, Unf=OtherCon []]
         $wloop_s9ea
           = \ (ww1_s9e8 :: Int#) ->
               case tagToEnum# @ Bool (># ww1_s9e8 ww_s9ep) of {
                 False ->
                   case remInt# ww1_s9e8 2# of {
                     __DEFAULT -> $wloop_s9ea (+# ww1_s9e8 1#);
                     0# ->
                       Main.Yield1
                         @ Int @ Int (GHC.Types.I# (+# ww1_s9e8 1#))
 (GHC.Types.I# ww1_s9e8)
                   };
                 True -> Main.Done1 @ Int @ Int
               }; } in
       joinrec {
         $wloop1_s9el [InlPrag=[0], Occ=LoopBreaker] :: Int# -> Int# ->
 Int#
         [LclId[JoinId(2)], Arity=2, Str=<S,U><S,U>, Unf=OtherCon []]
         $wloop1_s9el (ww1_s9ef :: Int#) (ww2_s9ej :: Int#)
           = case $wloop_s9ea ww2_s9ej of {
               Done1 -> ww1_s9ef;
               Yield1 s'_a497 x_a498 ->
                 case x_a498 of { GHC.Types.I# y_a66i ->
                 case s'_a497 of { GHC.Types.I# ww4_X9hA ->
                 jump $wloop1_s9el (+# ww1_s9ef y_a66i) ww4_X9hA
                 }
                 }
             }; } in
       jump $wloop1_s9el 0# 1#


 -- RHS size: {terms: 33, types: 9, coercions: 0, joins: 1/1}
 Main.$wchain2 [InlPrag=NOINLINE] :: Int# -> Int#
 [GblId, Arity=1, Caf=NoCafRefs, Str=<S,U>]
 Main.$wchain2
   = \ (ww_s9dZ :: Int#) ->
       joinrec {
         $wloop_s9dV [InlPrag=[0], Occ=LoopBreaker] :: Int# -> Int# -> Int#
         [LclId[JoinId(2)], Arity=2, Str=<S,U><S,U>, Unf=OtherCon []]
         $wloop_s9dV (ww1_s9dP :: Int#) (ww2_s9dT :: Int#)
           = case tagToEnum# @ Bool (># ww2_s9dT ww_s9dZ) of {
               False ->
                 case remInt# ww2_s9dT 2# of {
                   __DEFAULT -> jump $wloop_s9dV ww1_s9dP (+# ww2_s9dT 1#);
                   0# -> jump $wloop_s9dV (+# ww1_s9dP ww2_s9dT) (+#
 ww2_s9dT 1#)
                 };
               True -> ww1_s9dP
             }; } in
       jump $wloop_s9dV 0# 1#
 }}}

 The code was adapted from M. Snoyman's blog post "Iterators and Streams in
 Rust and Haskell".

--

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


More information about the ghc-tickets mailing list