Skip to content

Commit f00aa02

Browse files
coottreeowl
authored andcommitted
Make Map's and Set's: foldr' & firends ' more strict
* Map.foldr' * Map.foldl' * Map.foldrWithKey' * Map.foldlWithKey' * Set.foldr' * Set.foldl' They now evaluate intermediate results of the accumulator before appling the folding operator again. This patch contains tests based on `nothunks` library. Benchmark summary: Map old: ``` benchmarking foldlWithKey' ... benchmarked foldlWithKey' time 28.57 μs (28.00 μs .. 29.35 μs) 0.991 R² (0.979 R² .. 0.999 R²) mean 28.43 μs (28.12 μs .. 28.99 μs) std dev 1.358 μs (729.0 ns .. 2.101 μs) variance introduced by outliers: 27% (moderately inflated) benchmarking foldrWithKey' ... benchmarked foldrWithKey' time 80.25 ns (79.45 ns .. 81.13 ns) 0.998 R² (0.996 R² .. 0.999 R²) mean 79.84 ns (79.27 ns .. 80.68 ns) std dev 2.184 ns (1.572 ns .. 2.921 ns) variance introduced by outliers: 11% (moderately inflated) ``` Map new: ``` benchmarking foldlWithKey' ... benchmarked foldlWithKey' time 27.76 μs (27.15 μs .. 28.34 μs) 0.996 R² (0.992 R² .. 0.998 R²) mean 27.08 μs (26.84 μs .. 27.43 μs) std dev 964.2 ns (720.7 ns .. 1.350 μs) variance introduced by outliers: 18% (moderately inflated) benchmarking foldrWithKey' ... benchmarked foldrWithKey' time 74.02 ns (73.01 ns .. 75.82 ns) 0.998 R² (0.994 R² .. 1.000 R²) mean 73.14 ns (72.91 ns .. 73.80 ns) std dev 1.245 ns (434.3 ps .. 2.625 ns) ``` Set old: ```benchmarking member ... benchmarked member time 237.1 μs (231.5 μs .. 246.0 μs) 0.993 R² (0.986 R² .. 0.999 R²) mean 234.3 μs (232.1 μs .. 238.0 μs) std dev 9.031 μs (5.737 μs .. 15.46 μs) variance introduced by outliers: 20% (moderately inflated) ``` Set new: ``` benchmarking member ... benchmarked member time 219.4 μs (216.3 μs .. 222.6 μs) 0.999 R² (0.997 R² .. 1.000 R²) mean 221.3 μs (219.3 μs .. 225.3 μs) std dev 10.25 μs (5.408 μs .. 18.71 μs) variance introduced by outliers: 28% (moderately inflated) ```
1 parent 4d50a8d commit f00aa02

File tree

10 files changed

+188
-11
lines changed

10 files changed

+188
-11
lines changed

containers-tests/benchmarks/Map.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,9 @@ main = do
7272
, bench "insertLookupWithKey' present" $ whnf (insLookupWithKey' elems_even) m_even
7373
, bench "mapWithKey" $ whnf (M.mapWithKey (+)) m
7474
, bench "foldlWithKey" $ whnf (ins elems) m
75-
-- , bench "foldlWithKey'" $ whnf (M.foldlWithKey' sum 0) m
75+
, bench "foldlWithKey'" $ whnf (M.foldlWithKey' sum 0) m
7676
, bench "foldrWithKey" $ whnf (M.foldrWithKey consPair []) m
77+
, bench "foldrWithKey'" $ whnf (M.foldrWithKey' consPair []) m
7778
, bench "update absent" $ whnf (upd Just evens) m_odd
7879
, bench "update present" $ whnf (upd Just evens) m_even
7980
, bench "update delete" $ whnf (upd (const Nothing) evens) m

containers-tests/containers-tests.cabal

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,13 @@ library
4141
array >=0.4.0.0
4242
, base >=4.6 && <5
4343
, deepseq >=1.2 && <1.5
44+
if impl(ghc >= 8.6.0)
45+
build-depends:
46+
nothunks
47+
, QuickCheck
4448

4549
include-dirs: include
46-
hs-source-dirs: src
50+
hs-source-dirs: src, tests
4751
ghc-options: -O2 -Wall
4852
other-extensions:
4953
BangPatterns
@@ -79,6 +83,9 @@ library
7983
Utils.Containers.Internal.BitQueue
8084
Utils.Containers.Internal.BitUtil
8185
Utils.Containers.Internal.StrictPair
86+
if impl(ghc >= 8.6.0)
87+
exposed-modules:
88+
Utils.NoThunks
8289

8390
other-modules:
8491
Utils.Containers.Internal.Coercions
@@ -332,6 +339,12 @@ test-suite set-properties
332339
, test-framework-quickcheck2
333340
, transformers
334341

342+
if impl(ghc >= 8.6)
343+
build-depends:
344+
nothunks
345+
other-modules:
346+
Utils.NoThunks
347+
335348
test-suite intmap-lazy-properties
336349
default-language: Haskell2010
337350
hs-source-dirs: tests
@@ -475,6 +488,12 @@ test-suite map-strictness-properties
475488
other-modules:
476489
Utils.IsUnit
477490

491+
if impl(ghc >= 8.6)
492+
build-depends:
493+
nothunks
494+
other-modules:
495+
Utils.NoThunks
496+
478497
test-suite intmap-strictness-properties
479498
default-language: Haskell2010
480499
hs-source-dirs: tests
@@ -501,6 +520,12 @@ test-suite intmap-strictness-properties
501520
other-modules:
502521
Utils.IsUnit
503522

523+
if impl(ghc >= 8.6)
524+
build-depends:
525+
nothunks
526+
other-modules:
527+
Utils.NoThunks
528+
504529
test-suite intset-strictness-properties
505530
default-language: Haskell2010
506531
hs-source-dirs: tests
@@ -522,6 +547,12 @@ test-suite intset-strictness-properties
522547

523548
ghc-options: -Wall
524549

550+
if impl(ghc >= 8.6)
551+
build-depends:
552+
nothunks
553+
other-modules:
554+
Utils.NoThunks
555+
525556
test-suite listutils-properties
526557
default-language: Haskell2010
527558
hs-source-dirs: tests
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
module Utils.NoThunks (whnfHasNoThunks) where
2+
3+
import Data.Maybe (isNothing)
4+
5+
import NoThunks.Class (NoThunks, noThunks)
6+
import Test.QuickCheck (Property, ioProperty)
7+
8+
-- | Check that after evaluating the argument to weak head normal form there
9+
-- are no thunks.
10+
--
11+
whnfHasNoThunks :: NoThunks a => a -> Property
12+
whnfHasNoThunks a = ioProperty
13+
. fmap isNothing
14+
. noThunks []
15+
$! a

containers-tests/tests/intmap-strictness.hs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{-# LANGUAGE CPP #-}
12
{-# OPTIONS_GHC -fno-warn-orphans #-}
23

34
module Main (main) where
@@ -6,6 +7,9 @@ import Test.ChasingBottoms.IsBottom
67
import Test.Framework (Test, TestName, defaultMain, testGroup)
78
import Test.Framework.Providers.QuickCheck2 (testProperty)
89
import Test.QuickCheck (Arbitrary(arbitrary))
10+
#if __GLASGOW_HASKELL__ >= 806
11+
import Test.QuickCheck (Property)
12+
#endif
913
import Test.QuickCheck.Function (Fun(..), apply)
1014
import Test.Framework.Providers.HUnit
1115
import Test.HUnit hiding (Test)
@@ -16,6 +20,9 @@ import qualified Data.IntMap as L
1620
import Data.Containers.ListUtils
1721

1822
import Utils.IsUnit
23+
#if __GLASGOW_HASKELL__ >= 806
24+
import Utils.NoThunks
25+
#endif
1926

2027
instance Arbitrary v => Arbitrary (IntMap v) where
2128
arbitrary = M.fromList `fmap` arbitrary
@@ -101,6 +108,16 @@ pFromAscListStrict ks
101108
where
102109
elems = [(k, v) | k <- nubInt ks, v <- [undefined, undefined, ()]]
103110

111+
#if __GLASGOW_HASKELL__ >= 806
112+
pStrictFoldr' :: IntMap Int -> Property
113+
pStrictFoldr' m = whnfHasNoThunks (M.foldr' (:) [] m)
114+
#endif
115+
116+
#if __GLASGOW_HASKELL__ >= 806
117+
pStrictFoldl' :: IntMap Int -> Property
118+
pStrictFoldl' m = whnfHasNoThunks (M.foldl' (flip (:)) [] m)
119+
#endif
120+
104121
------------------------------------------------------------------------
105122
-- check for extra thunks
106123
--
@@ -184,6 +201,10 @@ tests =
184201
pInsertLookupWithKeyValueStrict
185202
, testProperty "fromAscList is somewhat value-lazy" pFromAscListLazy
186203
, testProperty "fromAscList is somewhat value-strict" pFromAscListStrict
204+
#if __GLASGOW_HASKELL__ >= 806
205+
, testProperty "strict foldr'" pStrictFoldr'
206+
, testProperty "strict foldl'" pStrictFoldl'
207+
#endif
187208
]
188209
, tExtraThunksM
189210
, tExtraThunksL

containers-tests/tests/intset-strictness.hs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,34 @@
1+
{-# LANGUAGE CPP #-}
2+
#if __GLASGOW_HASKELL__ >= 800
3+
{-# OPTIONS_GHC -Wno-orphans #-}
4+
#endif
15
module Main (main) where
26

37
import Prelude hiding (foldl)
48

59
import Test.ChasingBottoms.IsBottom
610
import Test.Framework (Test, defaultMain, testGroup)
711
import Test.Framework.Providers.QuickCheck2 (testProperty)
12+
import Test.QuickCheck (Arbitrary (..))
13+
#if __GLASGOW_HASKELL__ >= 806
14+
import Test.QuickCheck (Property)
15+
#endif
816

917
import Data.IntSet
1018

19+
#if __GLASGOW_HASKELL__ >= 806
20+
import Utils.NoThunks
21+
#endif
22+
23+
24+
{--------------------------------------------------------------------
25+
Arbitrary, reasonably balanced trees
26+
--------------------------------------------------------------------}
27+
instance Arbitrary IntSet where
28+
arbitrary = do{ xs <- arbitrary
29+
; return (fromList xs)
30+
}
31+
1132
------------------------------------------------------------------------
1233
-- * Properties
1334

@@ -18,6 +39,16 @@ pFoldlAccLazy :: Int -> Bool
1839
pFoldlAccLazy k =
1940
isn'tBottom $ foldl (\_ x -> x) (bottom :: Int) (singleton k)
2041

42+
#if __GLASGOW_HASKELL__ >= 806
43+
pStrictFoldr' :: IntSet -> Property
44+
pStrictFoldr' m = whnfHasNoThunks (foldr' (:) [] m)
45+
#endif
46+
47+
#if __GLASGOW_HASKELL__ >= 806
48+
pStrictFoldl' :: IntSet -> Property
49+
pStrictFoldl' m = whnfHasNoThunks (foldl' (flip (:)) [] m)
50+
#endif
51+
2152
------------------------------------------------------------------------
2253
-- * Test list
2354

@@ -27,6 +58,10 @@ tests =
2758
-- Basic interface
2859
testGroup "IntSet"
2960
[ testProperty "foldl is lazy in accumulator" pFoldlAccLazy
61+
#if __GLASGOW_HASKELL__ >= 806
62+
, testProperty "strict foldr'" pStrictFoldr'
63+
, testProperty "strict foldl'" pStrictFoldl'
64+
#endif
3065
]
3166
]
3267

containers-tests/tests/map-strictness.hs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
{-# LANGUAGE BangPatterns #-}
2+
{-# LANGUAGE CPP #-}
13
{-# OPTIONS_GHC -fno-warn-orphans #-}
24

35
module Main (main) where
@@ -6,6 +8,9 @@ import Test.ChasingBottoms.IsBottom
68
import Test.Framework (Test, TestName, defaultMain, testGroup)
79
import Test.Framework.Providers.QuickCheck2 (testProperty)
810
import Test.QuickCheck (Arbitrary(arbitrary))
11+
#if __GLASGOW_HASKELL__ >= 806
12+
import Test.QuickCheck (Property)
13+
#endif
914
import Test.QuickCheck.Function (Fun(..), apply)
1015
import Test.Framework.Providers.HUnit
1116
import Test.HUnit hiding (Test)
@@ -15,6 +20,9 @@ import qualified Data.Map.Strict as M
1520
import qualified Data.Map as L
1621

1722
import Utils.IsUnit
23+
#if __GLASGOW_HASKELL__ >= 806
24+
import Utils.NoThunks
25+
#endif
1826

1927
instance (Arbitrary k, Arbitrary v, Ord k) =>
2028
Arbitrary (Map k v) where
@@ -82,6 +90,26 @@ pInsertLookupWithKeyValueStrict f k v m
8290
not (isBottom $ M.insertLookupWithKey (const3 1) k bottom m)
8391
| otherwise = isBottom $ M.insertLookupWithKey (apply3 f) k bottom m
8492

93+
#if __GLASGOW_HASKELL__ >= 806
94+
pStrictFoldr' :: Map Int Int -> Property
95+
pStrictFoldr' m = whnfHasNoThunks (M.foldr' (:) [] m)
96+
#endif
97+
98+
#if __GLASGOW_HASKELL__ >= 806
99+
pStrictFoldl' :: Map Int Int -> Property
100+
pStrictFoldl' m = whnfHasNoThunks (M.foldl' (flip (:)) [] m)
101+
#endif
102+
103+
#if __GLASGOW_HASKELL__ >= 806
104+
pStrictFoldrWithKey' :: Map Int Int -> Property
105+
pStrictFoldrWithKey' m = whnfHasNoThunks (M.foldrWithKey' (\_ a as -> a : as) [] m)
106+
#endif
107+
108+
#if __GLASGOW_HASKELL__ >= 806
109+
pStrictFoldlWithKey' :: Map Int Int -> Property
110+
pStrictFoldlWithKey' m = whnfHasNoThunks (M.foldlWithKey' (\as _ a -> a : as) [] m)
111+
#endif
112+
85113
------------------------------------------------------------------------
86114
-- check for extra thunks
87115
--
@@ -162,6 +190,12 @@ tests =
162190
pInsertLookupWithKeyKeyStrict
163191
, testProperty "insertLookupWithKey is value-strict"
164192
pInsertLookupWithKeyValueStrict
193+
#if __GLASGOW_HASKELL__ >= 806
194+
, testProperty "strict foldr'" pStrictFoldr'
195+
, testProperty "strict foldl'" pStrictFoldl'
196+
, testProperty "strict foldrWithKey'" pStrictFoldrWithKey'
197+
, testProperty "strict foldlWithKey'" pStrictFoldlWithKey'
198+
#endif
165199
]
166200
, tExtraThunksM
167201
, tExtraThunksL
@@ -184,3 +218,4 @@ const2 x _ _ = x
184218

185219
const3 :: a -> b -> c -> d -> a
186220
const3 x _ _ _ = x
221+

containers-tests/tests/set-properties.hs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ import Control.Applicative (Applicative (..), (<$>))
2323
#endif
2424
import Control.Applicative (liftA2)
2525

26+
#if __GLASGOW_HASKELL__ >= 806
27+
import Utils.NoThunks (whnfHasNoThunks)
28+
#endif
29+
2630
main :: IO ()
2731
main = defaultMain [ testCase "lookupLT" test_lookupLT
2832
, testCase "lookupGT" test_lookupGT
@@ -104,6 +108,10 @@ main = defaultMain [ testCase "lookupLT" test_lookupLT
104108
, testProperty "powerSet" prop_powerSet
105109
, testProperty "cartesianProduct" prop_cartesianProduct
106110
, testProperty "disjointUnion" prop_disjointUnion
111+
#if __GLASGOW_HASKELL__ >= 806
112+
, testProperty "strict foldr" prop_strictFoldr'
113+
, testProperty "strict foldr" prop_strictFoldl'
114+
#endif
107115
]
108116

109117
-- A type with a peculiar Eq instance designed to make sure keys
@@ -690,3 +698,13 @@ prop_disjointUnion xs ys =
690698
isLeft :: Either a b -> Bool
691699
isLeft (Left _) = True
692700
isLeft _ = False
701+
702+
#if __GLASGOW_HASKELL__ >= 806
703+
prop_strictFoldr' :: Set Int -> Property
704+
prop_strictFoldr' m = whnfHasNoThunks (foldr' (:) [] m)
705+
#endif
706+
707+
#if __GLASGOW_HASKELL__ >= 806
708+
prop_strictFoldl' :: Set Int -> Property
709+
prop_strictFoldl' m = whnfHasNoThunks (foldl' (flip (:)) [] m)
710+
#endif

containers/src/Data/Map/Internal.hs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3274,8 +3274,8 @@ foldr f z = go z
32743274
foldr' :: (a -> b -> b) -> b -> Map k a -> b
32753275
foldr' f z = go z
32763276
where
3277-
go !z' Tip = z'
3278-
go z' (Bin _ _ x l r) = go (f x (go z' r)) l
3277+
go !z' Tip = z'
3278+
go z' (Bin _ _ x l r) = go (f x $! go z' r) l
32793279
{-# INLINE foldr' #-}
32803280

32813281
-- | /O(n)/. Fold the values in the map using the given left-associative
@@ -3300,8 +3300,10 @@ foldl f z = go z
33003300
foldl' :: (a -> b -> a) -> a -> Map k b -> a
33013301
foldl' f z = go z
33023302
where
3303-
go !z' Tip = z'
3304-
go z' (Bin _ _ x l r) = go (f (go z' l) x) r
3303+
go !z' Tip = z'
3304+
go z' (Bin _ _ x l r) =
3305+
let !z'' = go z' l
3306+
in go (f z'' x) r
33053307
{-# INLINE foldl' #-}
33063308

33073309
-- | /O(n)/. Fold the keys and values in the map using the given right-associative
@@ -3328,7 +3330,7 @@ foldrWithKey' :: (k -> a -> b -> b) -> b -> Map k a -> b
33283330
foldrWithKey' f z = go z
33293331
where
33303332
go !z' Tip = z'
3331-
go z' (Bin _ kx x l r) = go (f kx x (go z' r)) l
3333+
go z' (Bin _ kx x l r) = go (f kx x $! go z' r) l
33323334
{-# INLINE foldrWithKey' #-}
33333335

33343336
-- | /O(n)/. Fold the keys and values in the map using the given left-associative
@@ -3354,8 +3356,10 @@ foldlWithKey f z = go z
33543356
foldlWithKey' :: (a -> k -> b -> a) -> a -> Map k b -> a
33553357
foldlWithKey' f z = go z
33563358
where
3357-
go !z' Tip = z'
3358-
go z' (Bin _ kx x l r) = go (f (go z' l) kx x) r
3359+
go !z' Tip = z'
3360+
go z' (Bin _ kx x l r) =
3361+
let !z'' = go z' l
3362+
in go (f z'' kx x) r
33593363
{-# INLINE foldlWithKey' #-}
33603364

33613365
-- | /O(n)/. Fold the keys and values in the map using the given monoid, such that

containers/src/Data/Set/Internal.hs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -991,7 +991,7 @@ foldr' :: (a -> b -> b) -> b -> Set a -> b
991991
foldr' f z = go z
992992
where
993993
go !z' Tip = z'
994-
go z' (Bin _ x l r) = go (f x (go z' r)) l
994+
go z' (Bin _ x l r) = go (f x $! go z' r) l
995995
{-# INLINE foldr' #-}
996996

997997
-- | /O(n)/. Fold the elements in the set using the given left-associative
@@ -1014,7 +1014,9 @@ foldl' :: (a -> b -> a) -> a -> Set b -> a
10141014
foldl' f z = go z
10151015
where
10161016
go !z' Tip = z'
1017-
go z' (Bin _ x l r) = go (f (go z' l) x) r
1017+
go z' (Bin _ x l r) =
1018+
let !z'' = go z' l
1019+
in go (f z'' x) r
10181020
{-# INLINE foldl' #-}
10191021

10201022
{--------------------------------------------------------------------

0 commit comments

Comments
 (0)