[GHC] #13966: Skip-less stream fusion: a missed opportunity

GHC ghc-devs at haskell.org
Thu Sep 28 11:00:26 UTC 2017


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

Comment (by mpickering):

 Here is the exact file I am using.

 {{{
 {-# LANGUAGE ExistentialQuantification #-}
 module Main where

 import GHC.Prim

 import Criterion.Main
 import GHC.Prim

 main :: IO ()
 main = defaultMain [b1, b2] where
   b1 = bench "Skip-less" $ whnf chain1 x
   b2 = bench "Skip" $ whnf chain2 x
   x = 100000000


 --------------------------------------------------------------------------------

 data Step1 s a = Done1 | Yield1 s a
 data Stream1 a = forall s. Stream1 s (s -> Step1 s a)

 enumFromTo1 :: (Ord a, Num a) => a -> a -> Stream1 a
 enumFromTo1 start high = Stream1 start f where
   f i | i > high  = Done1
       | otherwise = Yield1 (i + 1) i

 filter1 :: (a -> Bool) -> Stream1 a -> Stream1 a
 filter1 predicate (Stream1 s0 next) = Stream1 s0 loop where
   loop s = case next s of
     Done1 -> Done1
     Yield1 s' x
       | predicate x -> Yield1 s' x
       | otherwise   -> loop s'

 sum1 :: Num a => Stream1 a -> a
 sum1 (Stream1 s0 next) = loop 0 s0 where
   loop total s = case next s of
     Done1 -> total
     Yield1 s' x -> loop (total + x) s'

 chain1 :: Int -> Int
 chain1 = sum1 . filter1 even . enumFromTo1 1

 --------------------------------------------------------------------------------

 data Step2 s a = Done2 | Skip2 s | Yield2 s a
 data Stream2 a = forall s. Stream2 s (s -> Step2 s a)

 enumFromTo2 :: (Ord a, Num a) => a -> a -> Stream2 a
 enumFromTo2 start high = Stream2 start f where
   f i | i > high  = Done2
       | otherwise = Yield2 (i + 1) i

 filter2 :: (a -> Bool) -> Stream2 a -> Stream2 a
 filter2 predicate (Stream2 s0 next) = Stream2 s0 loop where
   loop s = case next s of
     Done2 -> Done2
     Skip2 s' -> Skip2 s'
     Yield2 s' x
       | predicate x -> Yield2 s' x
       | otherwise   -> Skip2 s'

 sum2 :: Num a => Stream2 a -> a
 sum2 (Stream2 s0 next) = loop 0 s0 where
   loop total s = case next s of
     Done2 -> total
     Skip2 s' -> loop total s'
     Yield2 s' x -> loop (total + x) s'

 chain2 :: Int -> Int
 chain2 = sum2 . filter2 even . enumFromTo2 1
 }}}

 I modified the SAT pass to ignore information about static arguments,
 perform the SAT transformation and then check whether we created a join
 point. If we create a join point then we keep the transformed version,
 otherwise we leave the code as it was. (This is what you suggested in
 comment:8)

 I then compiled the above program with this transformation turned on.
 `chain2` was unaffected, the core is as before but the core for `chain1`
 changed quite a bit.

 It seems from running the benchmarks that `chain1` is better but I didn't
 look yet why this might be the case.

 I am building from a recent HEAD
 (11d9615e9f751d6ed084f1cb20c24ad6b408230e) so whether loopification is in
 there or not I don't know.

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


More information about the ghc-tickets mailing list