packages feed

argo-0.2022.2.2: source/benchmark/Main.hs

import qualified Argo
import qualified Control.DeepSeq as DeepSeq
import qualified Control.Exception as Exception
import qualified Control.Monad.Trans.Writer as Writer
import qualified Data.ByteString as ByteString
import qualified Data.ByteString.Builder as Builder
import qualified Data.ByteString.Lazy as LazyByteString
import qualified Data.List as List
import qualified Data.Text as Text
import qualified Data.Text.Encoding as Encoding
import qualified Test.Tasty as Tasty
import qualified Test.Tasty.Bench as Bench

main :: IO ()
main = Bench.defaultMain . Writer.execWriter $ do

    group "encode" $ do

        group "Null" $ do
            bench "null" Argo.Null encode

        group "Boolean" $ do
            bench "false" (Argo.Boolean False) encode
            bench "true" (Argo.Boolean True) encode

        group "Number" $ do
            bench "zero" (Argo.Number $ Argo.Decimal 0 0) encode

        group "String" $ do
            let f n = Argo.String . Text.replicate n $ Text.singleton 'a'
            bench "empty" (f 0) encode
            bench "1 character" (f 1) encode
            bench "10 characters" (f 10) encode
            bench "100 characters" (f 100) encode
            bench "1000 characters" (f 1000) encode
            bench "10000 characters" (f 10000) encode

        group "Array" $ do
            let f n = Argo.Array $ replicate n Argo.Null
            bench "empty" (f 0) encode
            bench "1 element" (f 1) encode
            bench "10 elements" (f 10) encode
            bench "100 elements" (f 100) encode
            bench "1000 elements" (f 1000) encode
            bench "10000 elements" (f 10000) encode

        group "Object" $ do
            let
                f n = Argo.Object . replicate n $ Argo.Member
                    (Argo.Name Text.empty)
                    Argo.Null
            bench "empty" (f 0) encode
            bench "1 element" (f 1) encode
            bench "10 elements" (f 10) encode
            bench "100 elements" (f 100) encode
            bench "1000 elements" (f 1000) encode
            bench "10000 elements" (f 10000) encode

    group "decode" $ do

        group "Null" $ do
            bench "null" (utf8 "null") decode

        group "Boolean" $ do
            bench "false" (utf8 "false") decode
            bench "true" (utf8 "true") decode

        group "Number" $ do
            bench "zero" (utf8 "0") decode

        group "String" $ do
            let f n = utf8 . wrap "\"" "\"" $ replicate n 'a'
            bench "empty" (f 0) decode
            bench "one byte" (utf8 "\"$\"") decode
            bench "two bytes" (utf8 "\"\xc2\xa2\"") decode
            bench "three bytes" (utf8 "\"\xe2\x82\xac\"") decode
            bench "four bytes" (utf8 "\"\xf0\x90\x8d\x88\"") decode
            bench "short escape" (utf8 "\"\\n\"") decode
            bench "long escape" (utf8 "\"\\u001f\"") decode
            bench "surrogate pair" (utf8 "\"\\ud834\\udd1e\"") decode
            bench "1 element" (f 1) decode
            bench "10 elements" (f 10) decode
            bench "100 elements" (f 100) decode
            bench "1000 elements" (f 1000) decode
            bench "10000 elements" (f 10000) decode

        group "Array" $ do
            let
                f n = utf8 . wrap "[" "]" . List.intercalate "," $ replicate
                    n
                    "null"
            bench "empty" (f 0) decode
            bench "1 element" (f 1) decode
            bench "10 elements" (f 10) decode
            bench "100 elements" (f 100) decode
            bench "1000 elements" (f 1000) decode
            bench "10000 elements" (f 10000) decode

        group "Object" $ do
            let
                f n = utf8 . wrap "{" "}" . List.intercalate "," $ replicate
                    n
                    "\"\":null"
            bench "empty" (f 0) decode
            bench "1 element" (f 1) decode
            bench "10 elements" (f 10) decode
            bench "100 elements" (f 100) decode
            bench "1000 elements" (f 1000) decode
            bench "10000 elements" (f 10000) decode

bench
    :: (DeepSeq.NFData a, DeepSeq.NFData b)
    => String
    -> a
    -> (a -> IO b)
    -> Writer.Writer [Bench.Benchmark] ()
bench name x f =
    Writer.tell
        . pure
        . Tasty.withResource (Exception.evaluate $ DeepSeq.force x) discard
        $ Bench.bench name
        . Bench.nfIO
        . (>>= f)

decode :: ByteString.ByteString -> IO Argo.Value
decode = either fail pure . Argo.decode

discard :: Applicative f => a -> f ()
discard = const $ pure ()

encode :: Argo.Value -> IO LazyByteString.ByteString
encode = pure . Builder.toLazyByteString . Argo.encode

group
    :: String
    -> Writer.Writer [Bench.Benchmark] a
    -> Writer.Writer [Bench.Benchmark] ()
group x = Writer.tell . pure . Bench.bgroup x . Writer.execWriter

utf8 :: String -> ByteString.ByteString
utf8 = Encoding.encodeUtf8 . Text.pack

wrap :: Monoid a => a -> a -> a -> a
wrap l r x = l <> x <> r