linear-base-0.4.0: bench/Data/Mutable/Array.hs
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LinearTypes #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NumericUnderscores #-}
{-# LANGUAGE QuantifiedConstraints #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE StandaloneKindSignatures #-}
-- Uncomment the line below to observe the generated (optimised) Core. It will
-- land in a file named “Array.dump-simpl”
-- {-# OPTIONS_GHC -ddump-simpl -ddump-to-file -dsuppress-all -dsuppress-uniques #-}
module Data.Mutable.Array (benchmarks) where
import Control.DeepSeq (rnf)
import qualified Data.Array.Mutable.Linear as Array.Linear
import qualified Data.Array.Mutable.Linear as Array.Linear.Array
import qualified Data.Foldable
import Data.Functor.Compose
import Data.Kind
import qualified Data.Sequence
import qualified Data.Unrestricted.Linear as Linear
import qualified Data.Vector
import Prelude.Linear (($), (&))
import qualified Prelude.Linear as Linear
import Test.Tasty.Bench
import Prelude hiding (($))
arr_size :: Int
arr_size = 1_000
benchmarks :: Benchmark
benchmarks =
bgroup
"arrays"
$ runImpls
[ bAlloc,
bToList,
bMap,
bReads,
bSets
]
--------------------------------------------------------------------------------
data Impl where
Impl :: String -> (forall arr. (ArrayThing arr) => arr Int %1 -> ()) -> Impl
runImpls :: [Impl] -> [Benchmark]
runImpls = map (runImpl arr_size)
runImpl :: Int -> Impl -> Benchmark
runImpl sz0 (Impl name impl) =
bgroup
name
[ bench "Data.Array.Mutable.Linear" $ whnf (runLinear impl) sz0,
bench "Data.Vector" $ whnf (runDataVector (cleanup impl)) sz0,
bench "Data.Sequence" $ whnf (runSequence (cleanup impl)) sz0
]
where
runLinear :: (Array.Linear.Array Int %1 -> ()) -> Int -> ()
runLinear cb sz = Linear.unur (Array.Linear.alloc sz 0 (\a -> Linear.move (cb a)))
runDataVector :: (Data.Vector.Vector Int -> ()) -> Int -> ()
runDataVector cb sz = cb (Data.Vector.replicate sz 0)
runSequence :: (Data.Sequence.Seq Int -> ()) -> Int -> ()
runSequence cb sz = cb (Data.Sequence.replicate sz 0)
{-# INLINE runImpl #-}
type ArrayThing :: (Type -> Type) -> Constraint
class ArrayThing arr where
size :: arr a %1 -> (Linear.Ur Int, arr a)
get :: Int -> arr a %1 -> (Linear.Ur a, arr a)
set :: Int -> a -> arr a %1 -> arr a
toList :: arr a %1 -> Linear.Ur [a]
amap :: (a -> b) -> arr a %1 -> arr b
-- | Note: I [Arnaud Spiwack] initially thought I could use
-- 'Consumable'/'consume' for this. But it doesn't work because the natural
-- 'consume' function for `Ur x` doesn't evaluate the `x` at all. We need to
-- evaluate the `x` in the 'Vector' instance.
force :: arr a %1 -> ()
type UArrayThing :: (Type -> Type) -> Constraint
class UArrayThing arr where
usize :: arr a -> Int
uget :: Int -> arr a -> a
uset :: Int -> a -> arr a -> arr a
utoList :: arr a -> [a]
uamap :: (a -> b) -> arr a -> arr b
uforce :: arr a -> ()
instance ArrayThing Array.Linear.Array where
size = Array.Linear.Array.size
get = Array.Linear.Array.unsafeGet
set = Array.Linear.Array.unsafeSet
toList = Array.Linear.Array.toList
amap = Array.Linear.Array.map
force = Linear.consume
instance (UArrayThing arr) => ArrayThing (Compose Linear.Ur arr) where
size (Compose (Linear.Ur arr)) = (Linear.Ur (usize arr), Compose (Linear.Ur arr))
get i (Compose (Linear.Ur arr)) = (Linear.Ur (uget i arr), Compose (Linear.Ur arr))
set i a (Compose (Linear.Ur arr)) = Compose (Linear.Ur (uset i a arr))
toList (Compose (Linear.Ur arr)) = Linear.Ur (utoList arr)
amap f (Compose (Linear.Ur arr)) = Compose (Linear.Ur (uamap f arr))
force (Compose (Linear.Ur arr)) = uforce arr
instance UArrayThing Data.Vector.Vector where
usize = Data.Vector.length
uget i v = v Data.Vector.! i
uset i a v = v Data.Vector.// [(i, a)]
utoList = Data.Vector.toList
uamap = Data.Vector.map
uforce = (`seq` ())
instance UArrayThing Data.Sequence.Seq where
usize = Data.Sequence.length
uget i s = Data.Sequence.index s i
uset = Data.Sequence.update
utoList = Data.Foldable.toList
uamap = fmap
-- I'm not sure about this one: on the one hand it forces the data structure
-- to be allocated. On the other hand, it will do an extra traversal. Maybe
-- there's a better comparison that can be done.
uforce s = (foldMap (\_ -> Strict) s) `seq` ()
cleanup :: ((Compose Linear.Ur f a) %1 -> b) -> (f a -> b)
cleanup k a = k (Compose (Linear.Ur a))
data Strict = Strict
instance Semigroup Strict where
Strict <> x = x
instance Monoid Strict where
mempty = Strict
--------------------------------------------------------------------------------
bToList :: Impl
bToList = Impl "toList" impl
where
impl :: (ArrayThing arr) => arr Int %1 -> ()
impl arr = arr & toList & Linear.lift rnf & Linear.unur
bMap :: Impl
bMap = Impl "map" impl
where
impl :: (ArrayThing arr) => arr Int %1 -> ()
impl arr =
case arr & amap (+ 1) & get 5 of
(Linear.Ur _, arr') -> force arr'
bReads :: Impl
bReads = Impl "reads" impl
where
impl :: (ArrayThing arr) => arr Int %1 -> ()
impl arr0 =
case size arr0 of
(Linear.Ur sz, arr) -> go 0 sz arr
where
go :: (ArrayThing arr) => Int -> Int -> arr Int %1 -> ()
go start end arr
| start < end =
case get start arr of
(Linear.Ur i, arr') -> i `Linear.seq` go (start + 1) end arr'
| otherwise = force arr
bAlloc :: Impl
bAlloc = Impl "alloc" impl
where
impl :: (ArrayThing arr) => arr Int %1 -> ()
impl = force
bSets :: Impl
bSets = Impl "successive writes (very unfair to vector)" impl
where
impl :: (ArrayThing arr) => arr Int %1 -> ()
impl arr0 =
case size arr0 of
(Linear.Ur sz, arr) -> go 0 sz arr
where
go :: (ArrayThing arr) => Int -> Int -> arr Int %1 -> ()
go start end arr
| start < end =
go (start + 1) end Linear.$ set start 42 arr
| otherwise = force arr