packages feed

pure-borrow-0.1.0.0: bench/suite/PureBorrow/Bench/Growable.hs

{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE ImpredicativeTypes #-}
{-# LANGUAGE QualifiedDo #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE NoImplicitPrelude #-}

module PureBorrow.Bench.Growable (
  test_growable,
  directContentUpdateLoop,
  fixedContentUpdateLoop,
  growableContentUpdateLoop,
) where

import Control.Functor.Linear qualified as Control
import Control.Monad.Borrow.Pure
import Control.Syntax.DataFlow qualified as DataFlow
import Data.STRef (newSTRef, readSTRef)
import Data.Vector qualified as V
import Data.Vector.Mutable qualified as MV
import Data.Vector.Mutable.Growable.Linear.Borrow qualified as Growable
import Data.Vector.Mutable.Linear.Borrow qualified as Fixed
import Prelude.Linear
import Test.Tasty.Bench hiding (defaultMain)
import Prelude qualified as NonLinear

directContentUpdateLoop :: V.Vector Int -> V.Vector Int
{-# NOINLINE directContentUpdateLoop #-}
directContentUpdateLoop input =
  V.modify
    ( \mutable -> do
        -- The header ref mirrors the growable vector's indirection, so the
        -- baseline pays for the same extra load the measured variants do.
        header <- newSTRef (V.length input, mutable)
        (logicalSize, contents) <- readSTRef header
        go logicalSize 0 contents
    )
    input
  where
    go !logicalSize !index contents
      | index >= logicalSize = NonLinear.pure ()
      | otherwise = do
          value <- MV.unsafeRead contents index
          MV.unsafeWrite contents index (value + 1)
          go logicalSize (index + 1) contents

fixedContentUpdateLoop :: V.Vector Int -> V.Vector Int
{-# NOINLINE fixedContentUpdateLoop #-}
fixedContentUpdateLoop input =
  unur $ linearly \linear -> DataFlow.do
    (ownerLinear, runLinear) <- dup linear
    runBO runLinear Control.do
      (initialVector, lend) <- borrowM (Fixed.fromVector input ownerLinear)
      updatedVector <- updateContents (V.length input) 0 initialVector
      let !() = consume updatedVector
      pureAfter $ Fixed.toVector (reclaim lend)

growableContentUpdateLoop :: V.Vector Int -> V.Vector Int
{-# NOINLINE growableContentUpdateLoop #-}
growableContentUpdateLoop input =
  unur $ linearly \linear -> DataFlow.do
    (ownerLinear, runLinear) <- dup linear
    runBO runLinear Control.do
      (initialVector, lend) <- borrowM (Growable.fromVector input ownerLinear)
      updatedVector <- Growable.withContent_ initialVector \contents ->
        updateContents (V.length input) 0 contents
      let !() = consume updatedVector
      pureAfter $ Growable.toVector (reclaim lend)

updateContents ::
  forall α.
  Int ->
  Int ->
  Mut α (Fixed.Vector Int) %1 ->
  BO α ()
{-# INLINE updateContents #-}
updateContents !logicalSize !index contents
  | index >= logicalSize = Control.pure (consume contents)
  | otherwise = Control.do
      ((), nextContents) <-
        Fixed.unsafeUpdate
          index
          (\ !value -> Control.pure ((), value + 1))
          contents
      updateContents logicalSize (index + 1) nextContents

test_growable :: [Benchmark]
test_growable =
  [ env
      (NonLinear.pure $ V.generate (1024 * 1024) (`NonLinear.rem` 1024))
      \input ->
        bgroup
          "growable/content-update"
          [ bench "direct/header-open-once" $ nf directContentUpdateLoop input
          , bench "pure-borrow/fixed" $ nf fixedContentUpdateLoop input
          , bench "pure-borrow/withContent_" $ nf growableContentUpdateLoop input
          ]
  ]