packages feed

bluefin-algae-0.1.0.0: bench/quadratic-counter.hs

{-# LANGUAGE
  BangPatterns,
  BlockArguments,
  RankNTypes,
  ScopedTypeVariables,
  TypeOperators #-}

-- Algebraic operations require traversing the stack,
-- which causes this quadratic behavior in left-recursive functions.

import Test.Tasty.Bench (Benchmark, Benchmarkable, bcompareWithin, bench, bgroup, defaultMain, nf)
import Bluefin.Eff (Eff, type (:>), runPureEff)
import Bluefin.Algae
import Bluefin.Algae.State

-- Left recursive counter
leftRecCounter :: z :> zz => Handler (State Int) z -> Int -> Eff zz ()
leftRecCounter _state 0 = pure ()
leftRecCounter state n = do
  leftRecCounter state (n - 1)
  modify state (+ 1)

-- Benchmarking harness

-- @bcompareQuadratic name tolerance factor f@:
-- Assert that @f factor@ runs @factor * factor@ times slower than @f 1@,
-- within a relative tolerance interval @[1 - tolerance, 1 + tolerance]@.
bcompareQuadratic :: String -> Double -> Int -> (Int -> Benchmarkable) -> Benchmark
bcompareQuadratic = bcompareAsymptotic (\x -> x * x)

-- Compare the benchmarks (f 1) and (f factor) with respect to a given growth function
bcompareAsymptotic :: (Double -> Double) -> String -> Double -> Int -> (Int -> Benchmarkable) -> Benchmark
bcompareAsymptotic growth name tolerance factor f = bgroup name
  [ bench "baseline" (f 1)
  , bcompareWithin lower upper (name ++ ".baseline") $
      bench ("x" ++ show factor) (f factor)
  ] where
    factor2 = growth (fromIntegral factor)
    lower = (1 - tolerance) * factor2
    upper = (1 + tolerance) * factor2

runQuadraticCounter :: Int -> Int
runQuadraticCounter n = runPureEff $ execState 0 \state -> leftRecCounter state n

testQuadraticCounter :: Benchmark
testQuadraticCounter = bcompareQuadratic "quadratic-counter" 0.2 10 (\factor ->
  nf runQuadraticCounter (100 * factor))

main :: IO ()
main = defaultMain [ testQuadraticCounter ]