@@ -17,46 +17,214 @@ module Main (main) where
18-------------------------------------------------------------------------------
18-------------------------------------------------------------------------------
20−import Control.DeepSeq (NFData(..), force)
20+import Control.DeepSeq (NFData(..))
21import Control.Exception (assert)
21import Control.Exception (assert)
22import Control.Monad (replicateM_)
22import Control.Monad (replicateM_)
23import GHC.Generics (Generic)
23import GHC.Generics (Generic)
24import System.Random (randomRIO)
24import System.Random (randomRIO)
25−import Test.QuickCheck
26+import Test.QuickCheck (Arbitrary, arbitrary)
30+import Data.Proxy (Proxy(..))
31+import Streamly.Internal.Data.Unbox
33+import Control.DeepSeq (force)
34+import Test.QuickCheck (oneof, generate)
27import Streamly.Internal.Data.Unbox (newBytes)
35import Streamly.Internal.Data.Unbox (newBytes)
28import Streamly.Internal.Data.Serialize
36import Streamly.Internal.Data.Serialize
29−#define USE_TH
37+#endif
30#ifdef USE_TH
39#ifdef USE_TH
41+import Streamly.Internal.Data.Unbox.TH
31import Streamly.Internal.Data.Serialize.TH
43import Streamly.Internal.Data.Serialize.TH
34import Gauge
47import Gauge
35import Streamly.Benchmark.Common
48import Streamly.Benchmark.Common
51+#define SERIALIZE_CLASS Unbox
52+#define DERIVE_CLASS deriveUnbox
53+#define SERIALIZE_OP pokeByteIndex
54+#define DESERIALIZE_OP peekByteIndex
56+#define SERIALIZE_CLASS Serialize
57+#define DERIVE_CLASS deriveSerialize
58+#define SERIALIZE_OP serialize
59+#define DESERIALIZE_OP deserialize
37-------------------------------------------------------------------------------
62-------------------------------------------------------------------------------
39-------------------------------------------------------------------------------
64-------------------------------------------------------------------------------
67+ deriving (Generic, Show, Eq)
70+instance SERIALIZE_CLASS Unit
72+$(DERIVE_CLASS ''Unit)
78+ deriving (Generic, Show, Eq)
81+instance SERIALIZE_CLASS Sum2
83+$(DERIVE_CLASS ''Sum2)
112+ deriving (Generic, Show, Eq)
115+instance SERIALIZE_CLASS Sum25
117+$(DERIVE_CLASS ''Sum25)
147+ deriving (Generic, Show)
150+instance SERIALIZE_CLASS Product25
152+$(DERIVE_CLASS ''Product25)
155+-- XXX derived Eq instance is not inlined
156+instance Eq Product25 where
159+ (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)
160+ (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)
187+-------------------------------------------------------------------------------
41-- Simple non-recursive ADT
189-- Simple non-recursive ADT
42data CustomDT1
190data CustomDT1
44 | CDT1C2 Int
192 | CDT1C2 Int
45− | CDT1C3 Int Bool
193+ | CDT1C3 Int Int
46 deriving (Generic, Show, Eq)
194 deriving (Generic, Show, Eq)
48#ifndef USE_TH
196#ifndef USE_TH
49−instance Serialize CustomDT1
197+instance SERIALIZE_CLASS CustomDT1
51−$(deriveSerialize ''CustomDT1)
199+$(DERIVE_CLASS ''CustomDT1)
202+-------------------------------------------------------------------------------
204+{-# INLINE getSize #-}
205+getSize :: forall a. SERIALIZE_CLASS a => a -> Int
207+getSize _ = sizeOf (Proxy :: Proxy a)
210+ case size :: Size a of
216+{-# INLINE sizeOfOnce #-}
217+sizeOfOnce :: forall a. Serialize a => a -> Int
218+sizeOfOnce val = getSize val
54-- Recursive ADT
220-- Recursive ADT
55data BinTree a
221data BinTree a
56 = Tree (BinTree a) (BinTree a)
222 = Tree (BinTree a) (BinTree a)
58 deriving (Show, Read, Eq, Generic)
224 deriving (Show, Read, Eq, Generic)
226+-- XXX If a is ConstSize we can use an array to represent the binary tree. If
227+-- the tree is balanced space wastage would be minimum.
60#ifndef USE_TH
228#ifndef USE_TH
61instance Serialize (BinTree a)
229instance Serialize (BinTree a)
@@ -78,6 +246,7 @@ mkBinTree = go (generate $ arbitrary)
78 go r 0 = Leaf <$> r
246 go r 0 = Leaf <$> r
79 go r n = Tree <$> go r (n - 1) <*> go r (n - 1)
247 go r n = Tree <$> go r (n - 1) <*> go r (n - 1)
81prop :: Property
250prop :: Property
82prop = forAll arbitrary (ioProperty . test)
251prop = forAll arbitrary (ioProperty . test)
@@ -96,20 +265,7 @@ prop = forAll arbitrary (ioProperty . test)
97runQC :: IO ()
266runQC :: IO ()
98runQC = quickCheckWith stdArgs {maxSuccess = 100} prop
267runQC = quickCheckWith stdArgs {maxSuccess = 100} prop
100−data Enumeration = Enum1 | Enum2 | Enum3 | Enum4 | Enum5
101− deriving (Show, Read, Eq, Generic)
103−instance NFData Enumeration where
104− rnf a = rnf a `seq` ()
106−instance Arbitrary Enumeration where
107− arbitrary = elements [Enum1, Enum2, Enum3, Enum4, Enum5]
110−instance Serialize Enumeration
112−$(deriveSerialize ''Enumeration)
115-------------------------------------------------------------------------------
271-------------------------------------------------------------------------------
@@ -117,41 +273,55 @@ $(deriveSerialize ''Enumeration)
117-------------------------------------------------------------------------------
273-------------------------------------------------------------------------------
119{-# INLINE pokeTimes #-}
275{-# INLINE pokeTimes #-}
120−pokeTimes :: forall a. Serialize a => a -> Int -> IO ()
276+pokeTimes :: forall a. SERIALIZE_CLASS a => a -> Int -> IO ()
121pokeTimes val times = do
277pokeTimes val times = do
278+ -- XXX Size computation should also be part of the loop measuring
279+ -- serialization cost.
122− let n =
280+ let n = getSize val
123− case size :: Size a of
126 arr <- newBytes n
281 arr <- newBytes n
127 replicateM_ times $ do
282 replicateM_ times $ do
128− serialize 0 arr val
283+ SERIALIZE_OP 0 arr val
130{-# INLINE peekTimes #-}
285{-# INLINE peekTimes #-}
131−peekTimes :: forall a. Serialize a => a -> Int -> IO ()
286+peekTimes :: forall a. (Eq a, SERIALIZE_CLASS a) => Int -> a -> Int -> IO ()
132−peekTimes val times = do
287+peekTimes n val times = do
134− case size :: Size a of
137 arr <- newBytes n
288 arr <- newBytes n
138− _ <- serialize 0 arr val
289+ _ <- SERIALIZE_OP 0 arr val
139 replicateM_ times $ do
290 replicateM_ times $ do
140− (_, _ :: a) <- deserialize 0 arr
291+#ifdef USE_UNBOX
296+ <- DESERIALIZE_OP 0 arr
297+ -- Ensure that we are actually constructing the type and using it.
298+ -- Otherwise we may just read the values and discard them.
299+ -- The comparison adds to the cost though.
302+ then error "peek: no match"
141− return ()
303+ else return ()
143{-# INLINE roundtrip #-}
305{-# INLINE roundtrip #-}
144−roundtrip :: forall a. (Eq a, Serialize a) => a -> Int -> IO ()
306+roundtrip :: forall a. (Eq a, SERIALIZE_CLASS a) => a -> Int -> IO ()
145roundtrip val times = do
307roundtrip val times = do
308+ -- XXX Size computation should also be part of the loop measuring
309+ -- serialization cost.
146− let n =
310+ let n = getSize val
147− case size :: Size a of
150 arr <- newBytes n
311 arr <- newBytes n
151 replicateM_ times $ do
312 replicateM_ times $ do
152− _ <- serialize 0 arr val
313+ _ <- SERIALIZE_OP 0 arr val
153− (_, val1) <- deserialize 0 arr
317+ (_, val1)
319+ <- DESERIALIZE_OP 0 arr
154 assert (val == val1) (pure ())
320 assert (val == val1) (pure ())
321+ -- So that the compiler does not optimize it out
323+ then error "roundtrip: no match"
156benchSink :: NFData b => String -> Int -> (Int -> IO b) -> Benchmark
326benchSink :: NFData b => String -> Int -> (Int -> IO b) -> Benchmark
157benchSink name times f = bench name (nfIO (randomRIO (times, times) >>= f))
327benchSink name times f = bench name (nfIO (randomRIO (times, times) >>= f))
@@ -160,35 +330,85 @@ benchSink name times f = bench name (nfIO (randomRIO (times, times) >>= f))
160-- Benchmarks
330-- Benchmarks
161-------------------------------------------------------------------------------
331-------------------------------------------------------------------------------
333+-- Times is scaled by the number of constructors to normalize
163−allBenchmarks :: BinTree Int -> BinTree Enumeration -> Int -> [Benchmark]
335+allBenchmarks :: Int -> [Benchmark]
336+allBenchmarks times =
338+allBenchmarks :: BinTree Int -> [Int] -> Int -> [Benchmark]
164−allBenchmarks tInt _tEnum times =
339+allBenchmarks tInt lInt times =
165− [ bgroup "poke"
341+ [ bgroup "sizeOf"
344+ bench "bintree-int" $ nf sizeOfOnce tInt
345+ , bench "list-int" $ nf sizeOfOnce lInt
166 [ benchSink "C1" times
349 [ benchSink "C1" times
167− (pokeTimes (CDT1C1 :: CustomDT1))
350+ (pokeTimes CDT1C1)
168− , benchSink "C2" times
351+ , benchSink "C2" (times `div` 2)
169− (pokeTimes ((CDT1C2 (5 :: Int)) :: CustomDT1))
352+ (pokeTimes (CDT1C2 5))
170− , benchSink "C3" times
353+ , benchSink "C3" (times `div` 3)
171− (pokeTimes ((CDT1C3 (5 :: Int) True) :: CustomDT1))
354+ (pokeTimes (CDT1C3 5 2))
355+ , benchSink "Sum2" times
357+ , benchSink "Sum25" times
359+ , benchSink "Product25" (times `div` 26)
360+ (pokeTimes (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))
362+ , benchSink "bintree-int" 1
364+ , benchSink "list-int" 1
173 , bgroup "peek"
368 , bgroup "peek"
369+ [ let !n = getSize Unit
370+ in benchSink "Unit" times (peekTimes n Unit)
371+ , let !n = getSize CDT1C1
174− [ benchSink "C1" times
372+ in benchSink "C1" times (peekTimes n CDT1C1)
175− (peekTimes (CDT1C1 :: CustomDT1))
373+ , let val = CDT1C2 5
176− , benchSink "C2" times
374+ !n = getSize val
177− (peekTimes ((CDT1C2 (5 :: Int)) :: CustomDT1))
375+ in benchSink "C2" (times `div` 2) (peekTimes n val)
376+ , let val = CDT1C3 5 2
178− , benchSink "C3" times
378+ in benchSink "C3" (times `div` 3) (peekTimes n val)
179− (peekTimes ((CDT1C3 (5 :: Int) True) :: CustomDT1))
379+ , let !n = getSize Sum21
380+ in benchSink "Sum2" times (peekTimes n Sum21)
381+ , let !n = getSize Sum2525
382+ in benchSink "Sum25" times (peekTimes n Sum2525)
383+ , 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
385+ in benchSink "Product25" (times `div` 26) (peekTimes n val)
387+ , let !n = getSize tInt
388+ in benchSink "bintree-int" 1 (peekTimes n tInt)
389+ , let !n = getSize lInt
390+ in benchSink "list-int" 1 (peekTimes n lInt)
181 , bgroup "roundtrip"
393 , bgroup "roundtrip"
182 [ benchSink "C1" times
394 [ benchSink "C1" times
183− (roundtrip (CDT1C1 :: CustomDT1))
395+ (roundtrip CDT1C1)
184− , benchSink "C2" times
396+ , benchSink "C2" (times `div` 2)
185− (roundtrip ((CDT1C2 (5 :: Int)) :: CustomDT1))
397+ (roundtrip (CDT1C2 5))
186− , benchSink "C3" times
398+ , benchSink "C3" (times `div` 3)
187− (roundtrip ((CDT1C3 (5 :: Int) True) :: CustomDT1))
399+ (roundtrip (CDT1C3 5 2))
400+ , benchSink "Sum2" times
402+ , benchSink "Sum25" times
404+ , benchSink "Product25" (times `div` 26)
405+ (roundtrip (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))
188 , benchSink "bintree-int" 1
407 , benchSink "bintree-int" 1
189 (roundtrip tInt)
408 (roundtrip tInt)
190− -- , benchSink "bintree-enum" 1
409+ , benchSink "list-int" 1
191− -- (roundtrip tEnum)
410+ (roundtrip lInt)
@@ -198,18 +418,38 @@ allBenchmarks tInt _tEnum times =
199main :: IO ()
419main :: IO ()
201−#ifndef FUSION_CHECK
421+#ifndef USE_UNBOX
202− runQC
422+ -- Approximately 100000 constructors
423+ -- Assuming Leaf nodes constitute two constructors (Leaf, Int) and internal
424+ -- nodes 1 level = log_2 (100001/3) + 1 = 16
203 !tInt <- force <$> mkBinTree 16
425 !tInt <- force <$> mkBinTree 16
205− -- !tEnum <- force <$> (mkBinTree 16 :: BinTree Enumeration)
427+ -- Approximately 100000 constructors, assuming two constructors (Cons, Int)
429+ let lInt = [1..50000 :: Int]
430+ let !len = length lInt -- evaluate the list
433+ -- This can take too much memory/CPU, need to restrict the test
436+ runWithCLIOpts defaultStreamSize allBenchmarks
206− runWithCLIOpts defaultStreamSize (allBenchmarks tInt undefined)
438+ len `seq` runWithCLIOpts defaultStreamSize (allBenchmarks tInt lInt)
208 -- Enable FUSION_CHECK macro at the beginning of the file
441 -- Enable FUSION_CHECK macro at the beginning of the file
209 -- Enable one benchmark below, and run the benchmark
442 -- Enable one benchmark below, and run the benchmark
210 -- Check the .dump-simpl output
443 -- Check the .dump-simpl output
211 let value = 100000
444 let value = 100000
212 -- peekTimes ((CDT1C2 (5 :: Int)) :: CustomDT1) value
445 -- peekTimes ((CDT1C2 (5 :: Int)) :: CustomDT1) value
446+ -- peekTimes (Sum2525) value
447+ -- 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
448+ -- !(tInt :: BinTree Int) <- force <$> mkBinTree 16
449+ -- print $ sizeOfOnce tInt
213− roundtrip ((CDT1C2 (5 :: Int)) :: CustomDT1) value
451+ -- roundtrip ((CDT1C2 (5 :: Int)) :: CustomDT1) value
452+ let !n = getSize lEnum
453+ in peekTimes n lEnum 1
214 return ()
454 return ()