packages feed

online-0.2.2.0: bench/bench.hs

{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# OPTIONS_GHC -Wall #-}
{-# OPTIONS_GHC -fno-warn-type-defaults #-}

import Online.Averages
import Online.Medians
import Options.Generic
import NumHask.Prelude hiding ((%), fromIntegral)
import Protolude (fromIntegral)
import Perf hiding (zero, Additive)
import Formatting
import qualified Data.Text as Text
import qualified Control.Foldl as L
import Data.Scientific
import Data.TDigest

data Opts = Opts
  { runs :: Maybe Int -- <?> "number of runs"
  , sumTo :: Maybe Int -- <?> "sum to this number"
  } deriving (Generic, Show)

instance ParseRecord Opts

-- | compute deciles
--
-- > c5 <- decile 5 <$> ticks n f a
--
deciles :: (Functor f, Foldable f) => Int -> f Cycle -> [Double]
deciles n xs =
  (\x -> fromMaybe 0 $ quantile x (tdigest (fromIntegral <$> xs) :: TDigest 25)) <$>
  ((/ fromIntegral n) . fromIntegral <$> [0 .. n]) :: [Double]

-- | compute a percentile
--
-- > c <- percentile 0.4 . fst <$> ticks n f a
--
percentile :: (Functor f, Foldable f) => Double -> f Cycle -> Double
percentile p xs = fromMaybe 0 $ quantile p (tdigest (fromIntegral <$> xs) :: TDigest 25)

expt' :: Int -> Format r (Scientific -> r)
expt' x = scifmt Exponent (Just x)

code :: [Text] -> Text
code cs = "\n```\n" <> Text.intercalate "\n" cs <> "\n```\n"

sumInt :: [Int] -> Int -> Int
sumInt xs n = foldl' (+) zero (take n xs)

sumDouble :: [Double] -> Int -> Double
sumDouble xs n = foldl' (+) zero (take n xs)

sumPoly :: (Enum b, MultiplicativeUnital b, Additive b) => [b] -> Int -> b
sumPoly xs n = foldl' (+) zero (take n xs)

sumSum :: [Double] -> Int -> Double
sumSum xs n = L.fold L.sum (take n xs)

sumInt' :: Int -> Int
sumInt' x = foldl' (+) zero [zero .. x]

sumDouble' :: Double -> Double
sumDouble' x = foldl' (+) zero [one .. x]

sumPoly' :: (Enum b, MultiplicativeUnital b, Additive b) => b -> b
sumPoly' x = foldl' (+) zero [one .. x]

avTestMain :: [Double] -> Int -> Double
avTestMain xs n = L.fold Main.av (take n xs)

av :: (Field a) => L.Fold a a
av = L.Fold step begin extract
  where
    begin = (zero, zero)
    step (s, c) a = (s + a, c + one)
    extract (s, c) = s / c
{-# INLINABLE av #-}

maTest :: [Double] -> Int -> Double
maTest xs n = L.fold (ma 0.99) (take n xs)

formatRun :: [Cycle] -> Text -> Text
formatRun cs label =
    sformat
          ((right 24 ' ' %. stext) % stext %
           (left 9 ' ' %. expt' 3) % " cycles")
          label
          (Text.intercalate " " $ sformat (left 7 ' ' %. expt' 3) <$>
           (\x -> scientific (fromIntegral x) 0) <$> take 5 cs)
          (fromFloatDigits $ percentile 0.4 cs)

formatRunHeader :: Text
formatRunHeader =
        sformat
          ((right 24 ' ' %. stext) %
           (left 7 ' ' %. stext) %
           (left 8 ' ' %. stext) %
           (left 8 ' ' %. stext) %
           (left 8 ' ' %. stext) %
           (left 8 ' ' %. stext) %
           (left 8 ' ' %. stext))
          "run"
          "first"
          "2nd"
          "3rd"
          "4th"
          "5th"
          "40th %"

run :: Functor f => Text -> f ([Cycle], b) -> f Text
run label t = (`formatRun` label) . fst <$> t

tick_Test :: FilePath -> IO ()
tick_Test f = do
  onetick <- tick_
  ticks' <- replicateM 10 tick_
  manyticks <- replicateM 1000000 tick_
  let avticks = average manyticks
  let qticks = deciles 10 manyticks
  let tick999 = percentile 0.999 manyticks
  let tick99999 = percentile 0.99999 manyticks
  let tick99 = percentile 0.99 manyticks
  let tick40 = percentile 0.4 manyticks
  writeFile f $
    code
      [ "one tick_: " <> show onetick <> " cycles"
      , "next 10: " <> show ticks'
      , "average over 1m: " <> sformat (fixed 2) avticks <> " cycles"
      , "99.999% perc: " <> sformat commas (floor tick99999 :: Integer)
      , "99.9% perc: " <> sformat (fixed 2) tick999
      , "99th perc:  " <> sformat (fixed 2) tick99
      , "40th perc:  " <> sformat (fixed 2) tick40
      , "[min, 10th, 20th, .. 90th, max]:"
      , mconcat (sformat (" " % expt' 4) . fromFloatDigits <$> qticks)
      ]

fMono :: Int -> Int
fMono x = foldl' (+) 0 [1 .. x]

fLambda :: Int -> Int
fLambda = \x -> foldl' (+) 0 [1 .. x]

fPoly :: (Enum b, Num b, Additive b) => b -> b
fPoly x = foldl' (+) 0 [1 .. x]

tickTest :: FilePath -> Int -> IO ()
tickTest f a' = do
  (t, resultPrime) <- tick fMono a'
  print resultPrime
  (t2,_) <- tick fMono a'
  writeFile f $
    code
      [ "sum to " <> show a'
      , "first measure: " <> show t <> " cycles"
      , "second measure: " <> show t2 <> " cycles"
      ]


ticksTest :: FilePath -> Int -> Int -> IO ()
ticksTest f a' n = do
  -- | various versions of tick
  rpure <- run "ticks" $ ticks n fMono a'
  rpurePoly <- run "ticks (poly)" $ ticks n fPoly a'
  rpureLambda <- run "ticks (lambda)" $ ticks n fLambda a'
  rio <- run "ticksIO" $ ticksIO n (pure $ fMono a')
  rioPoly <- run "ticksIO (poly)" $ ticksIO n (pure $ fPoly a')
  rioLambda <- run "ticksIO (lambda)" $ ticksIO n (pure $ fLambda a')

  writeFile f $
    code [ "sum to " <> show a' <> " n = " <> show n
         , formatRunHeader
         , rpure
         , rpureLambda
         , rpurePoly
         , rio
         , rioLambda
         , rioPoly
         ]

main :: IO ()
main = do
  o :: Opts <- getRecord "online performance benchmarking"
  let n = fromMaybe 100 (runs o)
  let a = fromMaybe 1000 (sumTo o)
  let !a' = fromIntegral a :: Double
  let !xs = [zero .. a]
  let !xs' = fromIntegral <$> xs :: [Double]
  _ <- warmup 100

  rSumInt' <- run "sumInt [0..]" $ ticks n sumInt' a
  rSumDouble' <- run "sumDouble [0..]" $ ticks n sumDouble' a'
  rSumPoly' <- run "sumPoly [0..]" $ ticks n sumPoly' a'
  rSumInt <- run "sum Int" $ ticks n (sumInt xs) a
  rSumDouble <- run "sum Double" $ ticks n (sumDouble xs') a
  rSumPoly <- run "sum Poly" $ ticks n (sumPoly xs') a
  rSumSum <- run "fold sum" $ ticks n (sumSum xs') a
  rAvTestMain <- run "fold av" $ ticks n (avTestMain xs') a
  rMaTest <- run "fold ma" $ ticks n (maTest xs') a
  rStdTest <- run "fold std" $ ticks n (\x -> L.fold (std 0.99) $ take x xs') a

  rMaL1Test <- run "fold maL1" $ ticks n (\x -> L.fold (maL1 0 0.01 0.99) $ take x xs') a
  rabsmaL1Test <- run "fold absmaL1" $ ticks n (\x -> L.fold (absmaL1 0 0.01 0.99) $ take x xs') a

  writeFile "other/perf.md" $
    code
      [ "sum to " <> sformat commas a
      , formatRunHeader
      , rSumInt'
      , rSumDouble'
      , rSumPoly'
      , rSumInt
      , rSumDouble
      , rSumPoly
      , rSumSum
      , rAvTestMain
      , rMaTest
      , rStdTest
      , rMaL1Test
      , rabsmaL1Test
      ]

  tick_Test "other/tick_.md"
  tickTest "other/tick.md" a
  ticksTest "other/ticks.md" a n
  pure ()