packages feed

perf-analysis-0.3.0: examples/examples.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleInstances #-}
{-# OPTIONS_GHC -Wall #-}
{-# OPTIONS_GHC -fno-warn-type-defaults #-}
{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
{-# OPTIONS_GHC -fno-warn-unused-do-bind #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE NoImplicitPrelude #-}

module Main where

import Readme.Lhs
import Options.Generic
import Perf
import Perf.Analysis
import Protolude
import qualified Data.Map as Map
import qualified Data.Text as Text
import qualified Data.Vector as V
import qualified Data.Vector.Storable as S
import qualified Data.Vector.Unboxed as U

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

instance ParseField [Int]

instance ParseRecord Opts

-- | useful as a benchmark versus the `ticks` functions being imported
ticksInFile :: (NFData b) => Int -> (a -> b) -> a -> IO ([Cycle], b)
ticksInFile n0 f a = go f a n0 []
  where
    go f a n ts
      | n <= 0 = pure (ts, f a)
      | otherwise = do
          (t,_) <- tick f a
          go f a (n - 1) (t:ts)

-- | interposition of a lambda
fLambda :: Int -> Int
fLambda = \x -> foldl' (+) 0 [1 .. x]

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

-- | polymorphic function version
fPoly :: (Integral b) => b -> b
fPoly x = foldl' (+) 0 [1 .. x]

main :: IO ()
main = do
  o :: Opts <- getRecord "perf examples"
  let !n = fromMaybe 1000 (runs o)
  let !a = fromMaybe 1000 (sumTo o)
  let !as = [1, 10, 100, 1000 :: Int]
  prewarmup <- tick_
  _ <- warmup 100
  onetick <- tick_
  ticks' <- replicateM 10 tick_
  manyticks <- replicateM 1000000 tick_
  let avticks = average manyticks
  let qticks = deciles 5 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

  -- perf basics
  (result, cs) <- runPerfT $
      perf "sum" cycles (pure $ foldl' (+) 0 [0 .. a])
  putStrLn (show (result, cs) :: Text)

  -- PerfT example
  -- prior to Perfification
  result <- do
      txt <- readFile "perf-analysis/examples/examples.hs"
      let n = Text.length txt
      let x = foldl' (+) 0 [1..n]
      putStrLn $ "sum of one to number of characters is: " <>
          (show x :: Text)
      pure (n, x)

  -- post-Perfification
  (result', ms) <- runPerfT $ do
          txt <- perf "file read" cycles $ readFile "perf-analysis/examples/examples.hs"
          n <- perf "length" cycles $ pure (Text.length txt)
          x <- perf "sum" cycles $ pure (foldl' (+) 0 [1..n])
          perf "print to screen" cycles $
              putStrLn $ "sum of one to number of characters is: " <>
              (show x :: Text)
          pure (n, x)

  when (result == result') $ print "PerfT has no effect on original computation"

  -- | tick tests
  (t, resultPrime) <- tick fMono a
  print resultPrime
  (t2,_) <- tick fMono a
  (t3,_) <- tick fMono a
  (t',_) <- tick' fMono a
  (tio,_) <- tickIO (pure $ fMono a)
  -- mystery: if tickNoinline is used at all, all tick computations slow down
  -- by a factor of 2 (or by a function call)
  -- (tn,_) <- tickNoinline fMono a
  t10 <- sequence $ replicate 10 (tick fMono a)
  t10io <- sequence $ replicate 10 (tickIO $ pure (fMono a))
  -- t10n <- sequence $ replicate 10 (tickNoinline fMono a)
  t10' <- sequence $ replicate 10 (tick fMono a)

  -- | ticks and ticksIO
  (rpure, _) <- ticks n fMono a
  (rpurePoly, _) <- ticks n fPoly a
  (rpureLambda, _) <- ticks n fLambda a
  (rio, _) <- ticksIO n (pure $ fMono a)
  (rioPoly, _) <- ticksIO n (pure $ fPoly a)
  (rioLambda, _) <- ticksIO n (pure $ fLambda a)

  -- | gaps
  gaps <- sequence $ (tickIO . ticks n fPoly) <$> as

  -- | ns testing
  css <-
    fmap (fmap fst) <$>
    sequence ((replicateM n . tick fMono) <$> as)
  (ts, _) <- ns (ticks n fMono) as

  -- | vectors
  let asl :: [Int]
      asl = [1 .. a]
  let suml :: [Int] -> Int
      suml = foldl' (+) 0
  (rlist, _) <- ticks n suml asl
  let sumv :: V.Vector Int -> Int
      sumv = V.foldl (+) 0
  let asv :: V.Vector Int =
        V.generate a identity
  (rboxed, _) <- ticks n sumv asv
  let sums :: S.Vector Int -> Int
      sums = S.foldl (+) 0
  let ass :: S.Vector Int =
        S.generate a identity
  (rstorable, _) <- ticks n sums ass
  let sumu :: U.Vector Int -> Int
      sumu = U.foldl (+) 0
  let asu :: U.Vector Int =
        U.generate a identity
  (runboxed, _) <- ticks n sumu asu

  -- WHNF
  (rnf, _) <- tick (fmap fMono) (Just a)
  (rwhnf, _) <- tick (fmap fMono) (Just a)
  (rnfs, _) <- ticks n (fmap fMono) (Just a)
  (rwhnfs, _) <- ticksWHNF n (fmap fMono) (Just a)
  (rnfio, _) <- tickIO (pure $ fmap fMono (Just a))
  (rwhnfio, _) <- tickWHNFIO (pure $ fmap fMono (Just a))
  (rnfsio, _) <- ticksIO n (pure $ fmap fMono (Just a))
  (rwhnfsio, _) <- ticksWHNFIO n (pure $ fmap fMono (Just a))

  void $ runOutput
    ("other/readme_.md", GitHubMarkdown)
    ("readme.md", GitHubMarkdown) $ do

    output "run" $ Native $ (:[]) $ table
      "run details"
      []
      [AlignLeft, AlignRight]
      [0, 0]
      [ ["number of runs", formatI 2 n]
      , ["accumulate to", formatI 2 a]
      , ["function", "foldl' (+) 0"]
      ]

    output "tick_" $ Native $ (:[]) $ table mempty ["stat", "cycles"] mempty mempty
      [ ["pre warmup", show prewarmup]
      , ["one tick_", show onetick]
      , ["next 10", show ticks']
      , ["average over one million", fixed 2 avticks]
      , ["99.999% perc", commas 0 tick99999]
      , ["99.9% perc", fixed 2 tick999]
      , ["99th perc", fixed 2 tick99]
      , ["40th perc", fixed 2 tick40]
      , ["[min, 20th, .. 80th, max]",
         Text.intercalate " " (formatF 4 <$> qticks)]
      ]

    output "tick" $ Native
      [ plain ("sum to " <> show a)
      , table mempty ["stat", "cycles"] mempty mempty
        [ ["first measure", show t]
        , ["second measure", show t2]
        , ["third measure", show t3]
        , ["tick'", show t']
        , ["tickIO", show tio]
        , ["tick * 10", show (fst <$> t10)]
        , ["tickIO * 10", show (fst <$> t10io)]
        , ["tick' * 10", show (fst <$> t10')]
        ]
      ]

    output "ticks" $ Native [formatRuns 3 2
      [ ("monomorphic", rpure)
      , ("includes lambda", rpureLambda)
      , ("polymorphic", rpurePoly)
      , ("ticksIO mono", rio)
      , ("ticksIO lambda", rioLambda)
      , ("ticksIO poly", rioPoly)
      ]]



    output "gaps" $ Native $ (:[]) $ table mempty
      ["number runs", "outside cycles", "inside cycles", "gap"]
      mempty mempty
      (zipWith (\a (co, (ci, _)) ->
                  [ formatI 1 a
                  , formatI 3 co
                  , formatI 3 (sum ci)
                  , formatI 3 (co - sum ci)
                  ]) as gaps)

    output "tickns" $ Native
      [ table mempty (["sum to:"] <> (show <$> as)) mempty mempty
        [ ["(replicateM n . tick fMono) <$> as"] <>
          (formatF 3 . percentile 0.5 <$> css)
        , ["ns (ticks n fMono) as"] <>
          (formatF 3 . percentile 0.5 <$> ts)
        ]
      ]

    output "vector" $ Native $ [plain ("sum to " <> show a)] <>
      [formatRuns 3 2 
      [ ("ticks list", rlist)
      , ("ticks boxed", rboxed)
      , ("ticks storable", rstorable)
      , ("ticks unboxed", runboxed)
      ]]

    output "whnf" $ Native
      [ plain ("sum to " <> show a)
      , table mempty ["function", "cycles"] mempty mempty
        [ ["tick", formatI 3 rnf]
        , ["tickWHNF", formatI 3 rwhnf]
        , formatRun "ticks" 3 3 rnfs
        , formatRun "ticksWHNF" 3 3 rwhnfs
        , ["tickIO", formatI 3 rnfio]
        , ["tickWHNFIO", formatI 3 rwhnfio]
        , formatRun "ticksIO" 3 3 rnfsio
        , formatRun "ticksWHNFIO" 3 3 rwhnfsio
        ]
      ]

    output "perf" $ Native
      [ plain "perf cycle measurements"
      , table mempty ["effect", "cycles"] mempty mempty
        ((\(t,c) -> [t, formatI 3 c]) <$> Map.toList ms)
      ]