Skip to content

Commit f055502

Browse files
committed
Improve performance of some Tree folds
This performance issue is avoided in Tree's foldr already. This change applies the improvement in a few other places, and adds an explanatory comment.
1 parent 85a1ab5 commit f055502

2 files changed

Lines changed: 20 additions & 9 deletions

File tree

containers-tests/benchmarks/Tree.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ main = do
4545
, bgroup "foldlMap1" $ forTs ts $ whnf (Foldable1.foldlMap1 id (+))
4646
]
4747
#endif
48+
, bgroup "leaves" $ forTs ts $ nf T.leaves
49+
, bgroup "edges" $ forTs ts $ nf T.edges
4850
, bgroup "PostOrder"
4951
[ bgroup "Foldable"
5052
[ bgroup "folds"

containers/src/Data/Tree.hs

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -211,10 +211,7 @@ instance Foldable Tree where
211211

212212
foldr f z = \t -> go t z -- Use a lambda to allow inlining with two arguments
213213
where
214-
go (Node x ts) = f x . foldr (\t k -> go t . k) id ts
215-
-- This is equivalent to the following simpler definition, but has been found to optimize
216-
-- better in benchmarks:
217-
-- go (Node x ts) z' = f x (foldr go z' ts)
214+
go (Node x ts) z' = f x (foldrTreeList go z' ts)
218215
{-# INLINE foldr #-}
219216

220217
foldl' f = go
@@ -243,6 +240,17 @@ instance Foldable Tree where
243240
product = foldlMap1' id (*)
244241
{-# INLINABLE product #-}
245242

243+
-- This is the same as List's foldr, but unlike GHC's implementation the z is
244+
-- passed along in go instead of go closing over it.
245+
-- When folding over a Tree this avoids a closure per Node, which results in
246+
-- significant reductions in time and allocations according to benchmarks.
247+
foldrTreeList :: (Tree a -> b -> b) -> b -> [Tree a] -> b
248+
foldrTreeList f = go
249+
where
250+
go z [] = z
251+
go z (t:ts) = f t (go z ts)
252+
{-# INLINE foldrTreeList #-}
253+
246254
#if MIN_VERSION_base(4,18,0)
247255
-- | Folds in pre-order.
248256
--
@@ -571,7 +579,7 @@ leaves :: Tree a -> [a]
571579
#ifdef __GLASGOW_HASKELL__
572580
leaves t = GHC.Exts.build $ \cons nil ->
573581
let go (Node x []) z = cons x z
574-
go (Node _ ts) z = foldr go z ts
582+
go (Node _ ts) z = foldrTreeList go z ts
575583
in go t nil
576584
{-# INLINE leaves #-} -- Inline for list fusion
577585
#else
@@ -607,8 +615,9 @@ leaves t =
607615
edges :: Tree a -> [(a, a)]
608616
#ifdef __GLASGOW_HASKELL__
609617
edges (Node x0 ts0) = GHC.Exts.build $ \cons nil ->
610-
let go p = foldr (\(Node x ts) z -> cons (p, x) (go x z ts))
611-
in go x0 nil ts0
618+
let go _ [] z = z
619+
go p (Node x ts : ts') z = cons (p, x) (go x ts (go p ts' z))
620+
in go x0 ts0 nil
612621
{-# INLINE edges #-} -- Inline for list fusion
613622
#else
614623
edges (Node x0 ts0) =
@@ -723,7 +732,7 @@ instance Foldable PostOrder where
723732

724733
foldr f z0 = \(PostOrder t) -> go t z0 -- Use a lambda to inline with two arguments
725734
where
726-
go (Node x ts) z = foldr go (f x z) ts
735+
go (Node x ts) z = foldrTreeList go (f x z) ts
727736
{-# INLINE foldr #-}
728737

729738
foldl' f z0 = \(PostOrder t) -> go z0 t -- Use a lambda to inline with two arguments
@@ -778,7 +787,7 @@ instance Foldable1.Foldable1 PostOrder where
778787
where
779788
go (Node x []) z = x :| z
780789
go (Node x (t:ts)) z =
781-
go t (foldr (\t' z' -> foldr (:) z' (PostOrder t')) (x:z) ts)
790+
go t (foldrTreeList (\t' z' -> foldr (:) z' (PostOrder t')) (x:z) ts)
782791

783792
maximum = Foldable.maximum
784793
{-# INLINABLE maximum #-}

0 commit comments

Comments
 (0)