{-# LANGUAGE BangPatterns #-} {-# LANGUAGE CPP #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PackageImports #-} {-# LANGUAGE RecordWildCards #-} #if __GLASGOW_HASKELL__ < 800 {-# OPTIONS_GHC -fno-warn-incomplete-patterns #-} #else {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} #endif {-# OPTIONS_GHC -fno-warn-orphans #-} module Main (main) where import Control.DeepSeq import Control.Exception import Data.ByteString as BS import qualified Data.ByteString.Lazy as BL import Data.List as L import Data.Maybe import Data.Typeable import System.Environment import Criterion.Main import GHC.Generics import "bencode" Data.BEncode as A import "bencoding" Data.BEncode as C import "bencoding" Data.BEncode.Internal as C import "bencoding" Data.BEncode.Types as C #ifdef BENCHMARK_ATTOBENCODE import Data.Attoparsec.ByteString as Atto import Data.AttoBencode as B import Data.AttoBencode.Parser as B #endif instance NFData A.BEncode where rnf (A.BInt i) = rnf i rnf (A.BString s) = rnf s rnf (A.BList l) = rnf l rnf (A.BDict m) = rnf m #ifdef BENCHMARK_ATTOBENCODE instance NFData B.BValue where rnf (B.BInt i) = rnf i rnf (B.BString s) = rnf s rnf (B.BList l) = rnf l rnf (B.BDict d) = rnf d #endif getRight :: Either String a -> a getRight = either error id data List a = Cons a (List a) | Nil deriving Generic instance C.BEncode a => C.BEncode (List a) instance NFData a => NFData (List a) where rnf Nil = () rnf (Cons x xs) = rnf (x, xs) replicate' :: Int -> a -> List a replicate' c x | c >= 0 = go c | otherwise = Nil where go 0 = Nil go n = Cons x $ go (pred n) {----------------------------------------------------------------------- -- Big dicts -----------------------------------------------------------------------} data Torrent = Torrent { tAnnounce :: !ByteString , tAnnounceList :: !(Maybe ByteString) , tComment :: !(Maybe ByteString) , tCreatedBy :: !(Maybe ByteString) , tCreationDate :: !(Maybe ByteString) , tEncoding :: !(Maybe ByteString) , tInfo :: !BDict , tPublisher :: !(Maybe ByteString) , tPublisherURL :: !(Maybe ByteString) , tSignature :: !(Maybe ByteString) } deriving (Show, Eq, Typeable) instance NFData Torrent where rnf Torrent {..} = () instance C.BEncode Torrent where toBEncode Torrent {..} = toDict $ "announce" .=! tAnnounce C..: "announce-list" .=? tAnnounceList C..: "comment" .=? tComment C..: "created by" .=? tCreatedBy C..: "creation date" .=? tCreationDate C..: "encoding" .=? tEncoding C..: "info" .=! tInfo C..: "publisher" .=? tPublisher C..: "publisher-url" .=? tPublisherURL C..: "signature" .=? tSignature C..: endDict fromBEncode = fromDict $ do Torrent <$>! "announce" <*>? "announce-list" <*>? "comment" <*>? "created by" <*>? "creation date" <*>? "encoding" <*>! "info" <*>? "publisher" <*>? "publisher-url" <*>? "signature" {----------------------------------------------------------------------- -- Main -----------------------------------------------------------------------} main :: IO () main = do (path : args) <- getArgs torrentFile <- BS.readFile path let lazyTorrentFile = BL.fromChunks [torrentFile] evaluate $ rnf (torrentFile, lazyTorrentFile) withArgs args $ defaultMain [ bench "decode/bencode" $ nf A.bRead lazyTorrentFile #ifdef BENCHMARK_ATTOBENCODE , bench "decode/AttoBencode" $ nf (getRight . Atto.parseOnly bValue) torrentFile #endif , bench "decode/bencoding" $ nf (getRight . C.parse) torrentFile , let Just v = A.bRead lazyTorrentFile in bench "encode/bencode" $ nf A.bPack v #ifdef BENCHMARK_ATTOBENCODE , let Right v = Atto.parseOnly bValue torrentFile in bench "encode/AttoBencode" $ nf B.encode v #endif , let Right v = C.parse torrentFile in bench "encode/bencoding" $ nf C.build v , bench "decode+encode/bencode" $ nf (A.bPack . fromJust . A.bRead) lazyTorrentFile #ifdef BENCHMARK_ATTOBENCODE , bench "decode+encode/AttoBencode" $ nf (B.encode . getRight . Atto.parseOnly bValue) torrentFile #endif , bench "decode+encode/bencoding" $ nf (C.build . getRight . C.parse) torrentFile , bench "list10000int/bencode/encode" $ nf (A.bPack . A.BList . L.map (A.BInt . fromIntegral)) [0..10000 :: Int] #ifdef BENCHMARK_ATTOBENCODE , bench "list10000int/attobencode/encode" $ nf B.encode [1..20000 :: Int] #endif , bench "list10000int/bencoding/encode" $ nf C.encode [1..20000 :: Int] , let d = A.bPack $ A.BList $ L.map A.BInt (L.replicate 1000 (0 :: Integer)) in d `seq` (bench "list1000int/bencode/decode" $ nf (fromJust . A.bRead :: BL.ByteString -> A.BEncode) d) , let d = BL.toStrict (C.encode (L.replicate 10000 ())) in d `seq` (bench "list10000unit/bencoding/decode" $ nf (C.decode :: BS.ByteString -> Either String [()]) d) , let d = BL.toStrict $ C.encode $ L.replicate 10000 (0 :: Int) in d `seq` (bench "list10000int/bencoding/decode" $ nf (C.decode :: BS.ByteString -> Either String [Int]) d) , let d = L.replicate 10000 0 in bench "list10000int/bencoding/encode>>decode" $ nf (getRight . C.decode . BL.toStrict . C.encode :: [Int] -> [Int] ) d , let d = replicate' 10000 0 in bench "list10000int/bencoding/encode>>decode/generic" $ nf (getRight . C.decode . BL.toStrict . C.encode :: List Int -> List Int) d , let Right !be = C.parse torrentFile id' x = let t = either error id (fromBEncode x) in toBEncode (t :: Torrent) !_unused@True = let Right t = C.decode torrentFile in if C.decode (BL.toStrict (C.encode t)) /= Right (t :: Torrent) then error "invalid instance: BEncode Torrent" else True replFn m f = go m where go 0 = id go n = f . go (pred n) in bench "bigdict" $ nf (replFn (1000 :: Int) id') be , let fn x = let Right t = C.decode x in t :: Torrent in bench "torrent/decode" $ nf fn torrentFile ]