Use foldr/build fusion for toList

This commit is contained in:
Harendra Kumar 2019-10-17 00:09:06 +05:30
parent 54acd41d8b
commit fbec11f24d
4 changed files with 47 additions and 6 deletions

View File

@ -86,7 +86,8 @@ main =
-}
, benchPureSink "<" Ops.ordInstance
, benchPureSink "min" Ops.ordInstanceMin
, benchPureSink "IsList.toList" GHC.toList
-- length is used to check for foldr/build fusion
, benchPureSink "length . IsList.toList" (length . GHC.toList)
, benchIOSink "foldl'" Ops.pureFoldl'
, benchIOSink "read" (S.drain . S.unfold A.read)
, benchIOSink "toStreamRev" (S.drain . IA.toStreamRev)

View File

@ -124,7 +124,8 @@ main =
, benchPureSink "<" Ops.ordInstance
, benchPureSink "min" Ops.ordInstanceMin
, benchPureSrc "IsList.fromList" Ops.sourceIsList
, benchPureSink "IsList.toList" GHC.toList
-- length is used to check for foldr/build fusion
, benchPureSink "length . IsList.toList" (length . GHC.toList)
, benchPureSrc "IsString.fromString" Ops.sourceIsString
, mkString `deepseq` (bench "readsPrec pure streams" $
nf Ops.readInstance mkString)

View File

@ -102,7 +102,7 @@ import Foreign.Storable (Storable(..))
import Prelude hiding (length, foldr, read, unlines, splitAt)
import Text.Read (readPrec, readListPrec, readListPrecDefault)
import GHC.Base (Addr#, nullAddr#, realWorld#)
import GHC.Base (Addr#, nullAddr#, realWorld#, build)
import GHC.Exts (IsList, IsString(..))
import GHC.ForeignPtr (ForeignPtr(..), newForeignPtr_)
import GHC.IO (IO(IO), unsafePerformIO)
@ -738,12 +738,33 @@ fromStreamD m = runFold write m
runFold (Fold step begin done) = D.foldlMx' step begin done
-}
-- Use foldr/build fusion to fuse with list consumers
-- This can be useful when using the IsList instance
{-# INLINE_LATE toListFB #-}
toListFB :: forall a b. Storable a => (a -> b -> b) -> b -> Array a -> b
toListFB c n Array{..} = go (unsafeForeignPtrToPtr aStart)
where
go p | p == aEnd = n
go p =
-- unsafeInlineIO allows us to run this in Identity monad for pure
-- toList/foldr case which makes them much faster due to not
-- accumulating the list and fusing better with the pure consumers.
--
-- This should be safe as the array contents are guaranteed to be
-- evaluated/written to before we peek at them.
let !x = unsafeInlineIO $ do
r <- peek p
touchForeignPtr aStart
return r
in c x (go (p `plusPtr` (sizeOf (undefined :: a))))
-- | Convert an 'Array' into a list.
--
-- @since 0.7.0
{-# INLINABLE toList #-}
{-# INLINE toList #-}
toList :: Storable a => Array a -> [a]
toList = foldr (:) []
toList s = build (\c n -> toListFB c n s)
instance (Show a, Storable a) => Show (Array a) where
{-# INLINE showsPrec #-}

View File

@ -68,6 +68,8 @@ where
import Control.Applicative (liftA2)
import Control.Monad (ap, when)
import Control.Monad.Trans (lift, MonadTrans)
import Data.Functor.Identity (Identity(..))
import GHC.Base (build)
import GHC.Types (SPEC(..))
import Prelude hiding (map, mapM, foldr, take, concatMap)
@ -344,10 +346,26 @@ foldrT f final (Stream step state) = go SPEC state
Skip s -> go SPEC s
Stop -> final
{-# INLINE toList #-}
{-# INLINE_NORMAL toList #-}
toList :: Monad m => Stream m a -> m [a]
toList = foldr (:) []
-- Use foldr/build fusion to fuse with list consumers
-- This can be useful when using the IsList instance
{-# INLINE_LATE toListFB #-}
toListFB :: (a -> b -> b) -> b -> Stream Identity a -> b
toListFB c n (Stream step state) = go state
where
go st = case runIdentity (step defState st) of
Yield x s -> x `c` go s
Skip s -> go s
Stop -> n
{-# RULES "toList Identity" toList = toListId #-}
{-# INLINE_EARLY toListId #-}
toListId :: Stream Identity a -> Identity [a]
toListId s = Identity $ build (\c n -> toListFB c n s)
-- XXX run begin action only if the stream is not empty.
{-# INLINE_NORMAL foldlMx' #-}
foldlMx' :: Monad m => (x -> a -> m x) -> m x -> (x -> m b) -> Stream m a -> m b