11{-# LANGUAGE BangPatterns #-}
22{-# LANGUAGE CPP #-}
3+ {-# LANGUAGE LambdaCase #-}
34{-# LANGUAGE ScopedTypeVariables #-}
45{-# LANGUAGE TypeApplications #-}
56{- |
67Here we test that GHC is able to optimize well construction of vector
78using monadic\/applicative actions. Well is understood as able to
89generate code which does not allocate except for buffer and some
910constant overhead.
11+
12+ This is test for GHC optimizer as well and older version fail this
13+ test. Thus we have to disable them. However we expect (or rather
14+ hope) that no regressions will appear in future versions.
1015-}
1116module Inspect.Alloc where
1217
1318import Control.Monad.ST
1419import Data.Int
20+ import Data.Word
21+ import Data.Char
1522-- import Data.Monoid
1623import Data.Functor.Identity
24+ import Foreign.Storable (sizeOf )
1725import Test.Tasty
1826import Test.Tasty.HUnit
1927import System.Mem
2028import Test.Alloc
29+ import Test.Ignore
2130
2231import qualified Data.Vector.Unboxed as VU
32+ import Inspect.Fusion
33+
2334
35+ minGHC :: Int -> TestTree -> TestTree
36+ minGHC n test
37+ | ghcVersion >= n = test
38+ | otherwise = ignoreTest test
39+ where
40+ ghcVersion = __GLASGOW_HASKELL__ :: Int
2441
2542tests :: TestTree
2643tests = testGroup " allocations"
2744 [ testGroup " traversable"
2845 [ testCase " IO"
2946 $ checkAllocations (linear 8 )
3047 $ whnfIO (VU. traverse (\ _ -> getAllocationCounter) vector)
31-
32- #if MIN_VERSION_base(4,17,0)
33- -- GHC<9.4 doesn't optimize well.
34- , testCase " ST"
48+ , minGHC 904 $ testCase " ST"
3549 $ checkAllocations (linear 8 )
3650 $ (\ v -> runST $ VU. traverse (pureST . fromIntegral ) v) `whnf` vector
37- #endif
38-
39- #if MIN_VERSION_base(4,15,0)
40- -- GHC<9.0 doesn't optimize this well. And there's no appetite
41- -- for finding out why. Thus it's disabled for them. We'll still
42- -- catch regression going forward.
43- , testCase " Identity"
51+ , minGHC 900 $ testCase " Identity"
4452 $ checkAllocations (linear 8 )
4553 $ VU. traverse (\ n -> Identity (10 * n)) `whnf` vector
46- #endif
47-
4854 -- NOTE: Naive traversal is lazy and allocated 2 words per element
4955 --
5056 -- , testCase "Const Sum"
@@ -55,20 +61,106 @@ tests = testGroup "allocations"
5561 [ testCase " IO"
5662 $ checkAllocations (linear 8 )
5763 $ whnfIO (VU. replicateM size getAllocationCounter)
58-
59- #if MIN_VERSION_base(4,17,0)
60- -- GHC<9.4 doesn't optimize well.
61- , testCase " ST"
64+ , minGHC 904 $ testCase " ST"
6265 $ checkAllocations (linear 8 )
6366 $ (\ sz -> runST $ VU. generateM sz pureST) `whnf` size
64- #endif
65-
67+ -- NOTE: No rewrite rule for Identity
68+ --
6669 -- , testCase "Identity"
6770 -- $ checkAllocations (linear 8)
6871 -- $ (\sz -> VU.generateM sz (\n -> Identity (fromIntegral n :: Int64))) `whnf` size
6972 ]
73+ , testGroup " Fusion"
74+ [ testGroup " transformers"
75+ [ allocWHNF " test_map" test_map vectorI
76+ , allocWHNF " test_imap" test_imap vectorI
77+ , allocWHNF " test_mapMaybe" test_mapMaybe vectorI
78+ , allocWHNF " test_cons" test_cons vectorI
79+ , allocWHNF " test_snoc" test_snoc vectorI
80+ -- FIXME: GHC does not fuse intermediate vectors in concatMap
81+ --
82+ -- , allocWHNF "test_concatMap_singleton" test_concatMap_singleton vectorI
83+ -- , allocWHNF "test_concatMap_replicate" test_concatMap_replicate vectorI
84+ , allocWHNF " test_appendL" (test_appendL vectorI) vectorI
85+ , allocWHNF " test_appendR" (test_appendR vectorI) vectorI
86+ , allocWHNF " test_indexed" test_indexed vectorI
87+ ]
88+ , testGroup " producers"
89+ [ allocWHNF " test_replicate" test_replicate size
90+ , allocWHNF " test_generate" test_generate size
91+ , allocWHNF " test_iterateN" test_iterateN size
92+ , allocWHNF " test_unfoldr" test_unfoldr size
93+ , allocWHNF " test_unfoldrN" test_unfoldrN size
94+ , allocWHNF " test_enumFromN" test_enumFromN size
95+ , allocWHNF " test_enumFromStepN" test_enumFromStepN size
96+
97+ , allocWHNF " test_enumFromTo[Int]" (test_enumFromTo @ Int fromIntegral 0 ) 100000
98+ , allocWHNF " test_enumFromTo[Int64]" (test_enumFromTo @ Int64 fromIntegral 0 ) 100000
99+ , allocWHNF " test_enumFromTo[Int32]" (test_enumFromTo @ Int32 fromIntegral 0 ) 100000
100+ , allocWHNF " test_enumFromTo[Int16]" (test_enumFromTo @ Int16 fromIntegral 0 ) maxBound
101+ , allocWHNF " test_enumFromTo[Word]" (test_enumFromTo @ Word fromIntegral 0 ) 100000
102+ , allocWHNF " test_enumFromTo[Word64]" (test_enumFromTo @ Word64 fromIntegral 0 ) 100000
103+ , allocWHNF " test_enumFromTo[Word32]" (test_enumFromTo @ Word32 fromIntegral 0 ) 100000
104+ , allocWHNF " test_enumFromTo[Word16]" (test_enumFromTo @ Word16 fromIntegral 0 ) maxBound
105+ , allocWHNF " test_enumFromTo[Float]" (test_enumFromTo @ Float round 0 ) 100000
106+ , allocWHNF " test_enumFromTo[Double]" (test_enumFromTo @ Double round 0 ) 100000
107+ , allocWHNF " test_enumFromTo[Char]" (test_enumFromTo @ Char ord (chr 32 )) (chr 8000 )
108+ -- FIXME: We don't have specializations for enumFromThenTo
109+ --
110+ -- , allocWHNF "test_enumFromThenTo" test_enumFromThenTo size
111+ ]
112+ , testGroup " consumers"
113+ [ allocWHNF " test_bang" test_bang vectorI
114+ , allocWHNF " test_safeBang" test_safeBang vectorI
115+ , allocWHNF " test_head" test_head vectorI
116+ , allocWHNF " test_last" test_last vectorI
117+ , allocWHNF " test_unsafeHead" test_unsafeHead vectorI
118+ , allocWHNF " test_unsafeLast" test_unsafeLast vectorI
119+ , allocWHNF " test_indexM" test_indexM vectorI
120+ , allocWHNF " test_headM" test_headM vectorI
121+ , allocWHNF " test_lastM" test_lastM vectorI
122+ , allocWHNF " test_unsafeHeadM" test_unsafeHeadM vectorI
123+ , allocWHNF " test_unsafeLastM" test_unsafeLastM vectorI
124+ ]
125+ , testGroup " update"
126+ [ allocVecWHNF " test_upd" (test_upd listUpd) vectorI
127+ , allocVecWHNF " test_update_1" (test_update_1 vectorIdx) vectorI
128+ , allocVecWHNF " test_update_2" (test_update_2 vectorI) vectorI
129+ , allocVecWHNF " test_update__1" (test_update__1 vectorI vectorI) vectorI
130+ , minGHC 904 $ allocVecWHNF " test_update__2" (test_update__2 vectorI vectorI) vectorI
131+ , allocVecWHNF " test_update__3" (test_update__3 vectorI vectorI) vectorI
132+ , allocVecWHNF " test_unsafeUpdate_1" (test_unsafeUpdate_1 vectorIdx) vectorI
133+ , allocVecWHNF " test_unsafeUpdate_2" (test_unsafeUpdate_2 vectorI) vectorI
134+ , allocVecWHNF " test_unsafeUpdate__1" (test_unsafeUpdate__1 vectorI vectorI) vectorI
135+ , minGHC 904 $ allocVecWHNF " test_unsafeUpdate__2" (test_unsafeUpdate__2 vectorI vectorI) vectorI
136+ , allocVecWHNF " test_unsafeUpdate__3" (test_unsafeUpdate__3 vectorI vectorI) vectorI
137+ , allocVecWHNF " test_accumulate_1" (test_accumulate_1 vectorIdx) vectorI
138+ , allocVecWHNF " test_accumulate_2" (test_accumulate_2 vectorI) vectorI
139+ , allocVecWHNF " test_accumulate__1" (test_accumulate__1 vectorI vectorI) vectorI
140+ , minGHC 904 $ allocVecWHNF " test_accumulate__2" (test_accumulate__2 vectorI vectorI) vectorI
141+ , allocVecWHNF " test_accumulate__3" (test_accumulate__3 vectorI vectorI) vectorI
142+ , allocVecWHNF " test_unsafeAccumulate_1" (test_unsafeAccumulate_1 vectorIdx) vectorI
143+ , minGHC 904 $ allocVecWHNF " test_unsafeAccumulate_2" (test_unsafeAccumulate_2 vectorI) vectorI
144+ , allocVecWHNF " test_unsafeAccumulate__1" (test_unsafeAccumulate__1 vectorI vectorI) vectorI
145+ , minGHC 904 $ allocVecWHNF " test_unsafeAccumulate__2" (test_unsafeAccumulate__2 vectorI vectorI) vectorI
146+ , allocVecWHNF " test_unsafeAccumulate__3" (test_unsafeAccumulate__3 vectorI vectorI) vectorI
147+ ]
148+ , testGroup " other"
149+ [ allocWHNF " test_concat" test_concat listVectorI
150+ ]
151+ ]
70152 ]
71153
154+ allocWHNF :: String -> (a -> b ) -> a -> TestTree
155+ {-# INLINE allocWHNF #-}
156+ allocWHNF name f a = testCase name $ checkAllocations constant (f `whnf` a)
157+
158+ allocVecWHNF :: String -> (a -> b ) -> a -> TestTree
159+ {-# INLINE allocVecWHNF #-}
160+ allocVecWHNF name f a
161+ = testCase name
162+ $ checkAllocations (linear (sizeOf (undefined :: Int ))) (f `whnf` a)
163+
72164
73165pureST :: Int -> ST s Int64
74166{-# NOINLINE pureST #-}
@@ -87,6 +179,23 @@ vector :: VU.Vector Int64
87179{-# NOINLINE vector #-}
88180vector = VU. generate size fromIntegral
89181
182+ vectorI :: VU. Vector Int
183+ {-# NOINLINE vectorI #-}
184+ vectorI = VU. generate size fromIntegral
185+
186+ vectorIdx :: VU. Vector (Int ,Int )
187+ {-# NOINLINE vectorIdx #-}
188+ vectorIdx = VU. map (\ i -> (i`div` 3 , i)) vectorI
189+
190+ listVectorI :: [VU. Vector Int ]
191+ {-# NOINLINE listVectorI #-}
192+ listVectorI = replicate 8 vectorI
193+
194+ listUpd :: [(Int ,Int )]
195+ {-# NOINLINE listUpd #-}
196+ listUpd = [(0 ,0 ), (1000 ,0 ), (100 ,0 )]
197+
198+
90199-- | N bytes per element + constant overhead. We also check that bound
91200-- is tight.
92201linear :: Int -> Range
0 commit comments