content: {-# LANGUAGE BangPatterns #-}
  
  -- | Microbenchmark to measure the difference in 'TypedByteArray' and 'Data.Vector.Unboxed.Vector' indexing performance.
  -- The latter is a slice into a 'ByteArray' which means that every time you retrieve and element, an offset
  -- needs to be added to your index.
  --
  -- To reproduce:
  --
  -- @
  -- stack bench alfred-margaret:uvector-vs-tba --ba '--output uvector-vs-tba.html'
  -- @
  --
  -- You can pass a greater @--time-limit@ (in the single quotes) to increase the number of iterations.
  --
  -- NOTE: 'readUVector' and 'genUVector' are marked @NOINLINE@ to prevent GHC optimizing away the indexing addition.
  -- 'readTba' and 'genTba' are marked @NOINLINE@ as well for fairness, altough it shouldn't make a difference there.
  module Main where
  
  import Control.Monad.ST (runST)
  import Criterion.Main (Benchmark, bench, bgroup, defaultMain, nf)
  import Data.Foldable (for_)
  import Text.Printf (printf)
  
  import qualified Data.Vector.Unboxed as UVector
  import qualified Data.Vector.Unboxed.Mutable as UMVector
  
  import qualified Data.TypedByteArray as TBA
  
  main :: IO ()
  main = defaultMain
    [ bgroup "tba" $ mkReadBenchs readTba genTba [7, 8]
    , bgroup "uvector" $ mkReadBenchs readUVector genUVector [7, 8]
    ]
  
  mkReadBenchs
    :: (Int -> a -> Int) -- ^ Function that reads from an array @n@ times.
    -> (Int -> a)        -- ^ Function that constructs an array of length @n@.
    -> [Int]             -- ^ Which powers of 10 to pass for @n@.
    -> [Benchmark]
  mkReadBenchs readPattern gen powers =
    [ bench (printf "%d reads" n) $ nf (readPattern n) $ gen n
    | n <- map (10^) powers
    ]
  
  {-# NOINLINE readTba #-}
  readTba :: Int -> TBA.TypedByteArray Int -> Int
  readTba !n !arr = go 0
    where
      go !i
        | i >= n    = 42
        | otherwise = go $ TBA.unsafeIndex arr i
  
  {-# NOINLINE readUVector #-}
  readUVector :: Int -> UVector.Vector Int -> Int
  readUVector !n !arr = go 0
    where
      go !i
        | i >= n    = 42
        | otherwise = go $ UVector.unsafeIndex arr i
  
  -- NOTE: We should probably measure pseudo-random access time as well, e.g. by shuffling the generated arrays.
  
  {-# NOINLINE genTba #-}
  genTba :: Int -> TBA.TypedByteArray Int
  genTba n = runST $ do
    mutArr <- TBA.newTypedByteArray n
    for_ [0 .. n-1] $ \i -> TBA.writeTypedByteArray mutArr i $ i + 1
    TBA.unsafeFreezeTypedByteArray mutArr
  
  -- | Generate an unboxed vector @v@ such that @v[i] == i + 1@.
  {-# NOINLINE genUVector #-}
  genUVector :: Int -> UVector.Vector Int
  genUVector n = runST $ do
    mutArr <- UMVector.new n
    for_ [0 .. n-1] $ \i -> UMVector.write mutArr i $ i + 1
    UVector.unsafeFreeze mutArr
  