packages feed

yamlet-1.0.0.0: bench/Yamlet/Bench/Libraries.hs

{-# LANGUAGE AllowAmbiguousTypes #-}

-- | The benchmarks that compare yamlet with the other libraries.
module Yamlet.Bench.Libraries
  ( libraryBenchmarks
  ) where

import Control.DeepSeq
import Data.Aeson qualified as J
import Data.ByteString qualified as BS
import Data.ByteString.Lazy qualified as BL
import Data.Map.Strict qualified as M
import Data.Text qualified as T
import Data.YAML qualified as H
import Data.YAML.Event qualified as HE
import Data.Yaml qualified as Y
import Test.Tasty.Bench

import Yamlet
import Yamlet.Bench.Inputs
import Yamlet.Bench.Types
import Yamlet.Syntax qualified as S

-- | The benchmarks are grouped by the operation, so that the times of the
-- libraries for one operation and input are next to each other.
libraryBenchmarks :: [Benchmark]
libraryBenchmarks =
  [ bgroup "parse" (map (uncurry parsing) inputs)
  , bgroup "render" (map (uncurry rendering) inputs)
  , bgroup
      "decode"
      [ decoding @[Config] "config" configInput []
      , decoding @[Json] "json" jsonInput [aesonDecoding @[Json] jsonInput]
      , decoding @(M.Map T.Text T.Text) "text" textInput []
      ]
  , bgroup
      "encode"
      [ encoding @[Config] "config" configInput []
      , encoding @[Json] "json" jsonInput [aesonEncoding @[Json] jsonInput]
      , encoding @(M.Map T.Text T.Text) "text" textInput []
      ]
  ]
  where
    -- The benchmarks that parse an input into the trees of each library.
    parsing :: String -> BS.ByteString -> Benchmark
    parsing name bs =
      bgroup
        name
        [ bgroup
            "yamlet"
            [ bench "syntax tree" $ nf S.parseDocuments bs
            , bench "values" $ nf (decodeAll @Value) bs
            ]
        , bgroup
            "HsYAML"
            [ bench "events" $ nf HE.parseEvents lazy
            , bench "nodes" $
                nf
                  ( foldMap (\(H.Doc n) -> forceNode n)
                      . either (error . show) id
                      . H.decodeNode
                  )
                  lazy
            ]
        , bgroup
            "yaml"
            [ bench "aeson value" $
                nf (either (error . show) id . Y.decodeEither' @J.Value) bs
            ]
        ]
      where
        lazy :: BL.ByteString
        lazy = BL.fromStrict bs

        -- HsYAML has no NFData instance for nodes.
        forceNode :: H.Node loc -> ()
        forceNode = \case
          H.Scalar _ s -> case s of
            H.SNull -> ()
            H.SBool b -> b `seq` ()
            H.SFloat d -> d `seq` ()
            H.SInt i -> i `seq` ()
            H.SStr t -> t `seq` ()
            H.SUnknown tag t -> tag `seq` t `seq` ()
          H.Mapping _ _ m -> foldMap (\(k, v) -> forceNode k `seq` forceNode v) (M.toList m)
          H.Sequence _ _ xs -> foldMap forceNode xs
          H.Anchor _ _ n -> forceNode n

    -- The benchmark that renders the syntax tree of an input.
    rendering :: String -> BS.ByteString -> Benchmark
    rendering name bs =
      bgroup name [bench "yamlet" $ nf (S.renderSyntax S.defaultRenderOptions) trees]
      where
        trees :: [S.Document]
        trees = either (error . show) id $ S.parseDocuments bs

    -- The benchmarks that decode an input into a value of the type, and the
    -- given benchmarks for other libraries.
    decoding
      :: forall a
       . (NFData a, FromYaml a, H.FromYAML a, J.FromJSON a)
      => String
      -> BS.ByteString
      -> [Benchmark]
      -> Benchmark
    decoding name bs others =
      bgroup name $
        [ bench "yamlet" $ nf (either (error . show) id . decode @a) bs
        , bench "HsYAML" $ nf (either (error . show) id . H.decode1Strict @a) bs
        , bench "yaml" $ nf (either (error . show) id . Y.decodeEither' @a) bs
        ]
          ++ others

    -- The benchmarks that encode the value of an input, decoded as the type,
    -- and the given benchmarks for other libraries.
    encoding
      :: forall a
       . (NFData a, FromYaml a, ToYaml a, H.ToYAML a, J.ToJSON a)
      => String
      -> BS.ByteString
      -> [Benchmark]
      -> Benchmark
    encoding name bs others =
      bgroup name $
        [ bench "yamlet" $ nf encode value
        , bench "HsYAML" $ nf H.encode1Strict value
        , bench "yaml" $ nf Y.encode value
        ]
          ++ others
      where
        value :: a
        value = either (error . show) id $ decode bs

    -- The benchmark that decodes a JSON input with aeson.
    aesonDecoding :: forall a. (NFData a, J.FromJSON a) => BS.ByteString -> Benchmark
    aesonDecoding bs = bench "aeson" $ nf (either error id . J.eitherDecodeStrict' @a) bs

    -- The benchmark that encodes the value of a JSON input as JSON with aeson.
    aesonEncoding
      :: forall a. (NFData a, FromYaml a, J.ToJSON a) => BS.ByteString -> Benchmark
    aesonEncoding bs = bench "aeson" $ nf J.encode value
      where
        value :: a
        value = either (error . show) id $ decode bs