{-# 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 #-}