ixset-typed-0.6: bench/Bench/Types.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}
-- | Sample data type and index declarations used by the benchmarks.
--
module Bench.Types
( -- * Element type
Entry(..)
, EntryId(..)
, Author(..)
, Updated(..)
, Tag(..)
, Priority(..)
, GenEntry(..)
-- * Index sets
, EntryIxs
, Entries
, Entries1
, Entries2
, Entries3
, SmallEntries
, GenEntries
-- * Deterministic test data
, mkEntries
, mkEntriesFrom
, mkEntry
, primaryTag
) where
import Control.DeepSeq (NFData(..))
import Data.Data (Data)
import Data.Proxy (Proxy(..))
import Data.IxSet.Typed
newtype EntryId = EntryId Int deriving (Eq, Ord, Show, Data)
newtype Author = Author String deriving (Eq, Ord, Show, Data)
newtype Updated = Updated Int deriving (Eq, Ord, Show, Data)
newtype Tag = Tag String deriving (Eq, Ord, Show, Data)
newtype Priority = Priority Int deriving (Eq, Ord, Show, Data)
-- The @Entry@ type is indexed on five keys with deliberately different
-- characteristics:
--
-- * 'EntryId' is unique (one element per key),
-- * 'Author' has moderate cardinality,
-- * 'Updated' has high cardinality and is queried with ranges,
-- * 'Tag' is multi-valued (each element occurs under several keys),
-- * 'Priority' has very low cardinality (few keys, huge buckets).
--
data Entry = Entry
{ eId :: EntryId
, eAuthor :: Author
, eUpdated :: Updated
, eTags :: [Tag]
, ePriority :: Priority
}
deriving (Eq, Ord, Show, Data)
-- | Same payload as 'Entry', but indexed via 'ixGen' rather than 'ixFun',
-- so that the cost of the SYB-based index extraction can be compared.
newtype GenEntry = GenEntry Entry
deriving (Eq, Ord, Show, Data)
instance NFData EntryId where rnf (EntryId i) = rnf i
instance NFData Author where rnf (Author s) = rnf s
instance NFData Updated where rnf (Updated i) = rnf i
instance NFData Tag where rnf (Tag s) = rnf s
instance NFData Priority where rnf (Priority i) = rnf i
instance NFData Entry where
rnf (Entry i a u ts p) = rnf i `seq` rnf a `seq` rnf u `seq` rnf ts `seq` rnf p
instance NFData GenEntry where
rnf (GenEntry e) = rnf e
type EntryIxs = '[EntryId, Author, Updated, Tag, Priority]
type Entries = IxSet EntryIxs Entry
instance Indexable EntryIxs Entry where
indices = ixList
(ixFun (\ e -> [eId e]))
(ixFun (\ e -> [eAuthor e]))
(ixFun (\ e -> [eUpdated e]))
(ixFun eTags)
(ixFun (\ e -> [ePriority e]))
-- | Prefixes of 'EntryIxs', for measuring how the cost of the various
-- operations scales with the number of declared indices.
type Entries1 = IxSet '[EntryId] Entry
type Entries2 = IxSet '[EntryId, Author] Entry
type Entries3 = IxSet '[EntryId, Author, Updated] Entry
instance Indexable '[EntryId] Entry where
indices = ixList (ixFun (\ e -> [eId e]))
instance Indexable '[EntryId, Author] Entry where
indices = ixList
(ixFun (\ e -> [eId e]))
(ixFun (\ e -> [eAuthor e]))
instance Indexable '[EntryId, Author, Updated] Entry where
indices = ixList
(ixFun (\ e -> [eId e]))
(ixFun (\ e -> [eAuthor e]))
(ixFun (\ e -> [eUpdated e]))
-- | Two indices declared with 'ixFun', to be compared against 'GenEntries'.
type SmallEntries = IxSet '[Author, Priority] Entry
instance Indexable '[Author, Priority] Entry where
indices = ixList
(ixFun (\ e -> [eAuthor e]))
(ixFun (\ e -> [ePriority e]))
-- | The same two indices declared with 'ixGen'.
type GenEntries = IxSet '[Author, Priority] GenEntry
instance Indexable '[Author, Priority] GenEntry where
indices = ixList
(ixGen (Proxy :: Proxy Author))
(ixGen (Proxy :: Proxy Priority))
--------------------------------------------------------------------------
-- Deterministic test data
--------------------------------------------------------------------------
-- | A cheap linear congruential generator. Benchmark data is generated
-- from a fixed seed so that runs are comparable across machines, without
-- depending on the @random@ package.
lcgs :: Int -> [Int]
lcgs = drop 1 . iterate step
where
step s = (s * 1103515245 + 12345) `mod` 2147483648
-- | @mkEntries n@ produces @n@ entries with distinct 'EntryId's, and
-- pseudo-random values for all other fields.
mkEntries :: Int -> [Entry]
mkEntries = mkEntriesFrom 0
-- | As 'mkEntries', but with 'EntryId's starting at the given offset.
-- Two calls with disjoint offsets produce disjoint sets of entries.
mkEntriesFrom :: Int -> Int -> [Entry]
mkEntriesFrom offset n =
zipWith mkEntry [offset .. offset + n - 1] (lcgs (offset + 1))
-- | @mkEntry i r@ is the entry with 'EntryId' @i@, with its remaining
-- fields derived from the seed @r@.
mkEntry :: Int -> Int -> Entry
mkEntry i r = Entry
{ eId = EntryId i
, eAuthor = Author ("author-" ++ show (r `mod` 64))
, eUpdated = Updated ((r `div` 64) `mod` 100000)
-- Multi-valued index: three tags per entry, occasionally coinciding.
, eTags = [ Tag ("tag-" ++ show ((r `div` k) `mod` 256)) | k <- [1, 11, 101] ]
, ePriority = Priority (r `mod` 5)
}
-- | The first tag of an entry. Entries built by 'mkEntry' always have some.
primaryTag :: Entry -> Tag
primaryTag e = case eTags e of
t : _ -> t
[] -> Tag "tag-0"