packages feed

benc-0.1.1.0: bench/Bench.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE OverloadedStrings #-}
module Main (main) where

import Test.Tasty.Bench

import Data.Bits
import Data.Int
import Data.Semigroup
import Data.Word
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as BC
import qualified Data.ByteString.Builder as BB
import qualified Data.ByteString.Lazy as BL
import qualified Data.Map.Strict as M
import qualified Data.Text as T
import qualified Data.Vector as V

import qualified Data.Bencode.Decode as D
import qualified Data.Bencode.Encode as E

main :: IO ()
main =  defaultMain
  [ bgroup "Decode"
    [ envPure sData   $ bench "string" . whnf decString
    , envPure iData   $ bench "integer" . whnf decInteger
    , envPure lData   $ bench "list" . whnf decList
    , envPure dData   $ bench "dict" . whnf decDict
    , envPure sData   $ bench "text" . whnf decText
    , envPure lsData  $ bench "list string" . whnf decListString
    , envPure liData  $ bench "list int" . whnf decListInt
    , envPure llData  $ bench "list list" . whnf decListList
    , envPure ldData  $ bench "list dict" . whnf decListDict
    , envPure ldData2 $ bench "list fields" . whnf decListFields
    , envPure liData  $ bench "list word16" . whnf decListWord16
    , envPure ldData2 $ bench "list dict'" . whnf decListDict'
    , envPure llData2 $ bench "list index" . whnf decListIndex
    , envPure llData2 $ bench "list list'" . whnf decListList'
    ]
  , bgroup "Encode"
    [ bench "string"      $ whnf encManyString n
    , bench "integer"     $ whnf encManyInteger n
    , bench "list"        $ whnf encManyList n100
    , bench "list fusion" $ whnf encManyListFusion n
    , bench "dict"        $ whnf encManyDict n100
    , bench "text"        $ whnf encManyText n
    , bench "int"         $ whnf encManyInt n
    , bench "field"       $ whnf encManyField n10
    , bench "word16"      $ whnf encManyWord16 n
    ]
  ]
  where
    -- How was the test data and size selected?
    -- Pretty much arbitrarily. Sizes are chosen such that a benchmark
    -- takes <= ~200ms.
    n, n10, n100 :: Int
    !n = 1000000
    !n10 = n `div` 10
    !n100 = n `div` 100

    envPure = env . pure
    toBS = BL.toStrict . BB.toLazyByteString

    sData = BC.pack (show n) <> ":" <> stimes n "x"
    iData = "i1" <> stimes n "1" <> "e"
    lData = "l" <> stimes n "le" <> "e"
    dData = toBS $ "d" <> go n10 <> "e"
      where
        go i | i == 2*n10 = mempty
        go i = "6:" <> BB.intDec i <> "de" <> go (i+1)
    lsData = toBS $ "l" <> stimes n10 s <> "e"
      where
        s = "5:hello5:world13:one two three"
    liData = toBS $ "l" <> go n <> "e"
      where
        go 0 = mempty
        go i = "i" <> BB.intDec (i .&. 0xffff) <> "e" <> go (i-1)
    llData = toBS $ "l" <> stimes n "le" <> "e"
    ldData = toBS $ "l" <> stimes n "de" <> "e"
    ldData2 = toBS $ "l" <> stimes n10 d <> "e"
      where
        d = "d1:0de1:1de1:2de1:3de1:4de1:5de1:6de1:7de1:8de1:9dee"
    silverForMonsters = "lelelelelelelelelele"
    llData2 = toBS $ "l" <> stimes n10 ("l" <> silverForMonsters <> "e") <> "e"


-- All bench functions below are marked NOINLINE to make it easy to find
-- them by name in the GHC core output.

------------------------------
-- Decode
------------------------------

decString :: B.ByteString -> B.ByteString
decString = runP D.string
{-# NOINLINE decString #-}

decInteger :: B.ByteString -> Integer
decInteger = runP D.integer
{-# NOINLINE decInteger #-}

decList :: B.ByteString -> V.Vector (V.Vector ())
decList = runP (D.list (D.list (pure ())))
{-# NOINLINE decList #-}

decDict :: B.ByteString -> M.Map B.ByteString (M.Map B.ByteString ())
decDict = runP (D.dict (D.dict (pure ())))
{-# NOINLINE decDict #-}

decText :: B.ByteString -> T.Text
decText = runP D.text
{-# NOINLINE decText #-}

decListString :: B.ByteString -> V.Vector B.ByteString
decListString = runP (D.list D.string)
{-# NOINLINE decListString #-}

decListInt :: B.ByteString -> V.Vector Int
decListInt = runP (D.list D.int)
{-# NOINLINE decListInt #-}

decListList :: B.ByteString -> V.Vector (V.Vector ())
decListList = runP (D.list (D.list (pure ())))
{-# NOINLINE decListList #-}

decListDict :: B.ByteString -> V.Vector (M.Map B.ByteString ())
decListDict = runP (D.list (D.dict (pure ())))
{-# NOINLINE decListDict #-}

decListFields :: B.ByteString -> V.Vector ()
decListFields = runP (D.list foo)
  where
    foo = do
      D.field "0" (pure ())
      D.field "1" (pure ())
      D.field "2" (pure ())
      D.field "3" (pure ())
      D.field "4" (pure ())
      D.field "5" (pure ())
      D.field "6" (pure ())
      D.field "7" (pure ())
      D.field "8" (pure ())
      D.field "9" (pure ())
{-# NOINLINE decListFields #-}

decListWord16 :: B.ByteString -> V.Vector Word16
decListWord16 = runP (D.list D.word16)
{-# NOINLINE decListWord16 #-}

decListDict' :: B.ByteString -> V.Vector ()
decListDict' = runP (D.list foo)
  where
    foo = D.dict' $ do
      D.field' "0" (pure ())
      D.field' "1" (pure ())
      D.field' "2" (pure ())
      D.field' "3" (pure ())
      D.field' "4" (pure ())
      D.field' "5" (pure ())
      D.field' "6" (pure ())
      D.field' "7" (pure ())
      D.field' "8" (pure ())
      D.field' "9" (pure ())
{-# NOINLINE decListDict' #-}

decListIndex :: B.ByteString -> V.Vector ()
decListIndex = runP (D.list foo)
  where
    foo = do
      D.index 0 (pure ())
      D.index 1 (pure ())
      D.index 2 (pure ())
      D.index 3 (pure ())
      D.index 4 (pure ())
      D.index 5 (pure ())
      D.index 6 (pure ())
      D.index 7 (pure ())
      D.index 8 (pure ())
      D.index 9 (pure ())
{-# NOINLINE decListIndex #-}

decListList' :: B.ByteString -> V.Vector ()
decListList' = runP (D.list foo)
  where
    foo = D.list' $ do
      D.elem (pure ())
      D.elem (pure ())
      D.elem (pure ())
      D.elem (pure ())
      D.elem (pure ())
      D.elem (pure ())
      D.elem (pure ())
      D.elem (pure ())
      D.elem (pure ())
      D.elem (pure ())
{-# NOINLINE decListList' #-}

runP :: D.Parser a -> B.ByteString -> a
runP p = either error id . D.decode p
{-# INLINE runP #-}

------------------------------
-- Encode
------------------------------

encManyString :: Int -> Int64
encManyString = encMany E.string ("hello","world","one two three")
{-# NOINLINE encManyString #-}

encManyInteger :: Int -> Int64
encManyInteger = encMany E.integer (0,-100000,fromIntegral (maxBound :: Int))
{-# NOINLINE encManyInteger #-}

encManyList :: Int -> Int64
encManyList =
  encMany (E.list (E.list E.value))
          (V.empty, V.replicate 10 V.empty, V.replicate 90 V.empty)
{-# NOINLINE encManyList #-}

encManyListFusion :: Int -> Int64
encManyListFusion = getL . E.toBuilder . E.list E.int . flip V.generate id
{-# NOINLINE encManyListFusion #-}

encManyDict :: Int -> Int64
encManyDict =
  encMany (E.dict (E.dict E.value))
          (M.empty, M.singleton "a" M.empty, m99)
{-# NOINLINE encManyDict #-}

m99 :: M.Map B.ByteString (M.Map B.ByteString a)
m99 = M.fromList [(BC.pack (show i), M.empty) | i <- [1..99 :: Int]]

encManyText :: Int -> Int64
encManyText = encMany E.text ("hello","world","one two three")
{-# NOINLINE encManyText #-}

encManyInt :: Int -> Int64
encManyInt = encMany E.int (0,-100000,maxBound)
{-# NOINLINE encManyInt #-}

data ABC = A | B | C

encManyField :: Int -> Int64
encManyField = encMany foo (A,B,C)
  where
    foo A = e
    foo B = E.dict' $ E.field "foo" id e
    foo C = E.dict' $
         E.field "0" id e
      <> E.field "1" id e
      <> E.field "2" id e
      <> E.field "3" id e
      <> E.field "4" id e
      <> E.field "5" id e
      <> E.field "6" id e
      <> E.field "7" id e
      <> E.field "8" id e
      <> E.field "9" id e
    e = E.dict id M.empty
{-# NOINLINE encManyField #-}

encManyWord16 :: Int -> Int64
encManyWord16 = encMany E.word16 (0,1000,maxBound)
{-# NOINLINE encManyWord16 #-}

encMany :: (a -> E.Encoding) -> (a,a,a) -> Int -> Int64
encMany enc (x0',x1',x2') = getL . go x0' x1' x2'
  where
    go _  _  _  0 = mempty
    go x0 x1 x2 i = E.toBuilder (enc x0) <> go x1 x2 x0 (i-1)
{-# INLINE encMany #-}

getL :: BB.Builder -> Int64
getL = BL.length . BB.toLazyByteString
{-# INLINE getL #-}