1{-# LANGUAGE TemplateHaskell #-}
5{-# OPTIONS_GHC -ddump-simpl -ddump-to-file -dsuppress-all #-}
9-- Module : Streamly.Benchmark.Data.Serialize
10-- Copyright : (c) 2023 Composewell
11-- License : BSD-3-Clause
12-- Maintainer : streamly@composewell.com
14module Main (main) where
16-------------------------------------------------------------------------------
18-------------------------------------------------------------------------------
20import Control.DeepSeq (NFData(..), deepseq)
21import GHC.Generics (Generic)
22import System.Random (randomRIO)
24import Test.QuickCheck (Arbitrary, arbitrary)
28import Data.Proxy (Proxy(..))
29import Streamly.Internal.Data.Unbox
31import Control.DeepSeq (force)
32import Test.QuickCheck (oneof, generate)
33import Streamly.Internal.Data.Unbox (newBytes, MutableByteArray)
34import Streamly.Internal.Data.Serialize hiding (encode)
39import Streamly.Internal.Data.Unbox.TH
41import Streamly.Internal.Data.Serialize.TH
46import Streamly.Benchmark.Common
49import Streamly.Benchmark.Data.Serialize.RecCompatible
50import Streamly.Benchmark.Data.Serialize.RecNonCompatible
54#define SERIALIZE_CLASS Unbox
55#define DERIVE_CLASS deriveUnbox
56#define SERIALIZE_OP pokeByteIndex
57#define DESERIALIZE_OP peekByteIndex
59#define SERIALIZE_CLASS Serialize
60#define DERIVE_CLASS deriveSerialize
61#define SERIALIZE_OP serialize
62#define DESERIALIZE_OP deserialize
65-------------------------------------------------------------------------------
67-------------------------------------------------------------------------------
70 deriving (Generic, Show, Eq)
73instance SERIALIZE_CLASS Unit
78instance NFData Unit where
85 deriving (Generic, Show, Eq)
88instance SERIALIZE_CLASS Sum2
93instance NFData Sum2 where
123 deriving (Generic, Show, Eq)
126instance SERIALIZE_CLASS Sum25
128$(DERIVE_CLASS ''Sum25)
131instance NFData Sum25 where
162 deriving (Generic, Show)
165instance SERIALIZE_CLASS Product25
167$(DERIVE_CLASS ''Product25)
170instance NFData Product25 where
173 (Product25 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 a16 a17 a18 a19 a20 a21 a22 a23 a24 a25)
201-- XXX derived Eq instance is not inlined
202instance Eq Product25 where
205 (Product25 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 a16 a17 a18 a19 a20 a21 a22 a23 a24 a25)
206 (Product25 b1 b2 b3 b4 b5 b6 b7 b8 b9 b10 b11 b12 b13 b14 b15 b16 b17 b18 b19 b20 b21 b22 b23 b24 b25)
233-------------------------------------------------------------------------------
234-- Simple non-recursive ADT
235-------------------------------------------------------------------------------
242 deriving (Generic, Show, Eq)
245instance SERIALIZE_CLASS CustomDT1
247$(DERIVE_CLASS ''CustomDT1)
250instance NFData CustomDT1 where
253 rnf (CDT1C2 i) = i `seq` ()
254 rnf (CDT1C3 i1 i2) = i1 `seq` i2 `seq` ()
257-------------------------------------------------------------------------------
259-------------------------------------------------------------------------------
263 = Tree (BinTree a) (BinTree a)
265 deriving (Show, Read, Eq, Generic)
268instance Serialize (BinTree a)
270$(deriveSerialize ''BinTree)
273instance NFData a => NFData (BinTree a) where
275 rnf (Leaf a) = rnf a `seq` ()
276 rnf (Tree l r) = rnf l `seq` rnf r `seq` ()
278instance Arbitrary a => Arbitrary (BinTree a) where
279 arbitrary = oneof [Leaf <$> arbitrary, Tree <$> arbitrary <*> arbitrary]
281mkBinTree :: (Arbitrary a) => Int -> IO (BinTree a)
282mkBinTree = go (generate $ arbitrary)
287 go r n = Tree <$> go r (n - 1) <*> go r (n - 1)
291-------------------------------------------------------------------------------
293-------------------------------------------------------------------------------
295{-# INLINE getSize #-}
296getSize :: forall a. SERIALIZE_CLASS a => a -> Int
298getSize _ = sizeOf (Proxy :: Proxy a)
303-------------------------------------------------------------------------------
305-------------------------------------------------------------------------------
307-- Parts of "f" that are dependent on val will not be optimized out.
309loop :: Int -> (a -> IO b) -> a -> IO ()
310loop count f val = go count val
315 then f x >> go (n-1) x
318-- The first arg of "f" is the environment which is not threaded around in the
320{-# INLINE loopWith #-}
321loopWith :: Int -> (e -> a -> IO b) -> e -> a -> IO ()
322loopWith count f e val = go count val
327 then f e x >> go (n-1) x
330benchSink :: NFData b => String -> Int -> (Int -> IO b) -> Benchmark
331benchSink name times f = bench name (nfIO (randomRIO (times, times) >>= f))
333-------------------------------------------------------------------------------
334-- Serialization Helpers
335-------------------------------------------------------------------------------
338{-# INLINE pokeWithSize #-}
339pokeWithSize :: SERIALIZE_CLASS a => MutableByteArray -> a -> IO ()
340pokeWithSize arr val = do
342 n `seq` SERIALIZE_OP 0 arr val >> return ()
344{-# INLINE pokeTimesWithSize #-}
345pokeTimesWithSize :: SERIALIZE_CLASS a => a -> Int -> IO ()
346pokeTimesWithSize val times = do
349 loopWith times pokeWithSize arr val
353poke :: SERIALIZE_CLASS a => MutableByteArray -> a -> IO ()
354poke arr val = SERIALIZE_OP 0 arr val >> return ()
356{-# INLINE pokeTimes #-}
357pokeTimes :: SERIALIZE_CLASS a => a -> Int -> IO ()
358pokeTimes val times = do
361 loopWith times poke arr val
364encode :: SERIALIZE_CLASS a => a -> IO ()
368 SERIALIZE_OP 0 arr val >> return ()
370{-# INLINE encodeTimes #-}
371encodeTimes :: SERIALIZE_CLASS a => a -> Int -> IO ()
372encodeTimes val times = loop times encode val
375peek :: forall a. (NFData a, SERIALIZE_CLASS a) =>
376 (a, Int) -> MutableByteArray -> IO ()
378peek (_val, _) arr = do
379 (val1 :: a) <- DESERIALIZE_OP 0 arr
381peek (_val, n) arr = do
382 (_, val1 :: a) <- DESERIALIZE_OP 0 arr n
384 -- If the datatype is not deeply strict or deepseq is not used then use
386 -- Ensure that we are actually constructing the type and using it. This
387 -- is important, otherwise the structure is created and discarded, the
388 -- cost of creation of the structure is not accounted. Otherwise we may
389 -- just read the values and discard them. The comparison adds to the
390 -- cost though. We could use deepseq but then we need to write
391 -- instances of NFData and ensure that they are correct and perform
392 -- well. Equality check also ensures correctness.
395 then error "peek: no match"
398 val1 `deepseq` return ()
400{-# INLINE peekTimes #-}
401peekTimes :: (NFData a, SERIALIZE_CLASS a) => Int -> a -> Int -> IO ()
402peekTimes n val times = do
404 _ <- SERIALIZE_OP 0 arr val
405 loopWith times peek (val, n) arr
408trip :: forall a. (NFData a, SERIALIZE_CLASS a) => a -> IO ()
412 _ <- SERIALIZE_OP 0 arr val
414 (val1 :: a) <- DESERIALIZE_OP 0 arr
416 (_, val1 :: a) <- DESERIALIZE_OP 0 arr n
418 -- Do not remove this or use deepseq, see the comments in peek.
421 then error "roundtrip: no match"
424 -- Note: deepseq is not needed if the datatype is strict
425 val1 `deepseq` return ()
427{-# INLINE roundtrip #-}
428roundtrip :: (NFData a, SERIALIZE_CLASS a) => a -> Int -> IO ()
429roundtrip val times = loop times trip val
431-------------------------------------------------------------------------------
433-------------------------------------------------------------------------------
435{-# INLINE benchConst #-}
438 -> (forall a. (NFData a, SERIALIZE_CLASS a) =>
439 Int -> a -> Int -> IO ())
442benchConst gname f times =
444 [ let !n = getSize Unit
445 in benchSink "Unit" times (f n Unit)
447 , let !n = getSize CDT1C1
448 in benchSink "C1" times (f n CDT1C1)
451 in benchSink "C2" (times `div` 2) (f n val)
452 , let val = CDT1C3 5 2
454 in benchSink "C3" (times `div` 3) (f n val)
456 , let !n = getSize Sum21
457 in benchSink "Sum2" times (f n Sum21)
458 , let !n = getSize Sum2525
459 in benchSink "Sum25" times (f n Sum2525)
460 , let val = Product25 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
462 in benchSink "Product25" (times `div` 26) (f n val)
466{-# INLINE benchVar #-}
469 -> (forall a. (NFData a, SERIALIZE_CLASS a) =>
470 Int -> a -> Int -> IO ())
475benchVar gname f tInt lInt times =
477 [ let !n = getSize tInt
478 in benchSink "bintree-int" times (f n tInt)
479 , let !n = getSize lInt
480 in benchSink "list-int" times (f n lInt)
484{-# INLINE benchGeneric #-}
486 :: (NFData b, Serialize b)
489 -> (forall a. (NFData a, Serialize a) =>
490 Int -> a -> Int -> IO ())
494benchGeneric gname valId f val times =
496 [ let !n = getSize val
497 in benchSink valId times (f n val)
501{-# INLINE benchLargeRec #-}
504 -> (forall a. (NFData a, Serialize a) =>
505 Int -> a -> Int -> IO ())
510benchLargeRec gname f recL recR times =
512 [ let !n = getSize recL
513 in benchSink "RecNonCompatible" times (f n recL)
514 , let !n = getSize recR
515 in benchSink "RecCompatible" times (f n recR)
519-- Times is scaled by the number of constructors to normalize
521allBenchmarks :: Int -> [Benchmark]
525 BinTree Int -> [Int] -> RecNonCompatible -> RecCompatible -> Int -> [Benchmark]
526allBenchmarks tInt lInt recL recR times =
531 bench "bintree-int" $ nf getSize tInt
532 , bench "list-int" $ nf getSize lInt
535 , benchConst "poke" (const pokeTimes) times
536 -- , benchConst "pokeWithSize" (const pokeTimesWithSize) times
537 , benchConst "encode" (const encodeTimes) times
538 , benchConst "peek" peekTimes times
539 , benchConst "roundtrip" (const roundtrip) times
541 , benchVar "poke" (const pokeTimes) tInt lInt 1
542 -- , benchVar "pokeWithSize" (const pokeTimesWithSize) tInt lInt 1
543 , benchVar "encode" (const encodeTimes) tInt lInt 1
544 , benchVar "peek" peekTimes tInt lInt 1
545 , benchVar "roundtrip" (const roundtrip) tInt lInt 1
546 , benchLargeRec "poke" (const pokeTimes) recL recR times
547 , benchLargeRec "encode" (const encodeTimes) recL recR times
548 , benchLargeRec "peek" peekTimes recL recR times
549 , benchLargeRec "roundtrip" (const roundtrip) recL recR times
553-------------------------------------------------------------------------------
555-------------------------------------------------------------------------------
560 -- Approximately 100000 constructors
561 -- Assuming Leaf nodes constitute two constructors (Leaf, Int) and internal
562 -- nodes 1 level = log_2 (100001/3) + 1 = 16
563 !(tInt :: BinTree Int) <- force <$> mkBinTree 16
565 let !recL = valRecNonCompatible
566 let !recR = valRecCompatible
568 -- Approximately 100000 constructors, assuming two constructors (Cons, Int)
570 let lInt = [1..50000 :: Int]
571 let !len = length lInt -- evaluate the list
574 -- This can take too much memory/CPU, need to restrict the test
577 runWithCLIOpts defaultStreamSize allBenchmarks
582 (allBenchmarks tInt lInt recL recR)
586 -- Enable FUSION_CHECK macro at the beginning of the file
587 -- Enable one benchmark below, and run the benchmark
588 -- Check the .dump-simpl output
590 -- print $ getSize (CDT1C3 4 2)
591 -- print $ getSize tInt
592 -- print $ getSize lInt
596 -- peekTimes ((CDT1C2 (5 :: Int)) :: CustomDT1) value
597 -- peekTimes (Sum2525) value
598 -- peekTimes (Product25 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25) value
600 let !n = getSize lInt
603 -- roundtrip ((CDT1C2 (5 :: Int)) :: CustomDT1) value
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list