{-# LANGUAGE DataKinds #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeFamilies #-} {-# OPTIONS_GHC -Wno-orphans #-} import Control.DeepSeq (NFData (rnf)) import Criterion.Main import Crypto.Hash (HashAlgorithm, SHA256) import Crypto.Hash.CompactSparseMerkleTree import Data.ByteArray (ByteArrayAccess) import Data.ByteString (ByteString) import qualified Data.ByteString.Char8 as BS import Data.Foldable (toList) import Data.List.NonEmpty (NonEmpty ((:|))) import Data.String (IsString) import Test.QuickCheck (Arbitrary (arbitrary), Gen) import qualified Test.QuickCheck as QC type BenchmarkCSMT = CSMT 'NonEmpty SHA256 Data instance NFData a => NFData (CSMT i alg a) where rnf = \case Nil {} -> rnf () Leaf {digest, value} -> rnf digest `seq` rnf value Parent {left, digest, maxDigest, right} -> rnf left `seq` rnf digest `seq` rnf maxDigest `seq` rnf right newtype Data = Data ByteString deriving (IsString, Eq, Show, ByteArrayAccess, NFData) instance Arbitrary Data where arbitrary = fmap (Data . BS.pack . ("data-" <>) . show . QC.getNonNegative) (QC.arbitrary :: QC.Gen (QC.NonNegative Integer)) instance Arbitrary (CSMT 'Empty alg a) where arbitrary = return empty instance (Arbitrary a, ByteArrayAccess a, HashAlgorithm alg) => Arbitrary (CSMT 'NonEmpty alg a) where arbitrary = do arbitrary >>= \case QC.NonEmpty (x : xs) -> return $ fromList $ x :| xs _ -> error "impossible" treeOfSize :: Integer -> Gen BenchmarkCSMT treeOfSize 1 = return $ singleton "data-0" treeOfSize n = do let x = Data $ BS.pack $ "data-" <> show (n - 1) insert x <$> treeOfSize (n - 1) membershipTestOfSize :: Integer -> Gen (Data, BenchmarkCSMT) membershipTestOfSize n = do t <- treeOfSize n x <- QC.oneof [elementIn t, elementNotIn t] return (x, t) elementIn :: BenchmarkCSMT -> Gen Data elementIn = QC.elements . toList elementNotIn :: BenchmarkCSMT -> Gen Data elementNotIn t = arbitrary `QC.suchThat` (`notMember` t) main :: IO () main = defaultMain [ bgroup "CSMT" [ benchSizes (upTo 10000) "maximumDigest" (QC.generate . treeOfSize) $ nf maximumDigest, benchSizes (upTo 10000) "minimumDigest" (QC.generate . treeOfSize) $ nf minimumDigest, benchSizes (upTo 1000) "member" (QC.generate . membershipTestOfSize) $ \ ~(x, t) -> nf (member x) t, benchSizes (upTo 1000) "insert" (QC.generate . membershipTestOfSize) $ \ ~(x, t) -> nf (insert x) t ] ] benchSizes :: (NFData t, Show a) => [a] -> String -> (a -> IO t) -> (t -> Benchmarkable) -> Benchmark benchSizes ns name e b = bgroup name $ flip map ns $ \n -> env (e n) $ \x -> bench (show n) $ b x upTo :: Integral a => a -> [a] upTo n = go [n] where go (x : xs) | x > 10 = go (x `div` 10 : x : xs) go xs = xs