Skip to content

Commit fd13f18

Browse files
committed
List Functor: mix unrolled and reverse map
Addresses purescript#131 The relevant chunk sizes (5 for the initial list segment), (3 for the tail-recursive remainder) were arrived at through benchmarked experimentation, mapping a simple (_ + 1) through lists of various sizes. Relevant figures: list of 1000 elems: 142.61 μs -> 36.97 μs list of 2000 elems: 275.17 μs -> 55.33 μs list of 10000 elems: 912.73 μs -> 208.39 μs list of 100000 elems: 34.56 ms -> 1.24 ms The ~30x speed increase for long lists is probably explained by the lack of GC thrashing with this approach.
1 parent 6c8aaad commit fd13f18

File tree

1 file changed

+41
-2
lines changed

1 file changed

+41
-2
lines changed

src/Data/List/Types.purs

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import Control.Extend (class Extend)
1010
import Control.MonadPlus (class MonadPlus)
1111
import Control.MonadZero (class MonadZero)
1212
import Control.Plus (class Plus)
13-
1413
import Data.Eq (class Eq1, eq1)
1514
import Data.Foldable (class Foldable, foldl, foldr, intercalate)
1615
import Data.Maybe (Maybe(..))
@@ -65,7 +64,47 @@ instance monoidList :: Monoid (List a) where
6564
mempty = Nil
6665

6766
instance functorList :: Functor List where
68-
map f = foldr (\x acc -> f x : acc) Nil
67+
map = listMap
68+
69+
-- chunked list Functor inspired by OCaml
70+
-- https://discuss.ocaml.org/t/a-new-list-map-that-is-both-stack-safe-and-fast/865
71+
-- chunk sizes determined through experimentation
72+
listMap :: forall a b. (a -> b) -> List a -> List b
73+
listMap f = startUnrolledMap naiveMapLimit
74+
where
75+
-- iterate the naive unrolled map up to 1000 times,
76+
-- which hits up to 5000 elements
77+
naiveMapLimit = 1000
78+
79+
startUnrolledMap :: Int -> List a -> List b
80+
startUnrolledMap 0 (x : xs) = f x : chunkedRevMap xs
81+
startUnrolledMap n (x1 : x2 : x3 : x4 : x5 : xs) =
82+
f x1 : f x2 : f x3 : f x4 : f x5 : startUnrolledMap (n - 1) xs
83+
startUnrolledMap n (x1 : x2 : x3 : x4 : xs) =
84+
f x1 : f x2 : f x3 : f x4 : startUnrolledMap (n - 1) xs
85+
startUnrolledMap n (x1 : x2 : x3 : xs) =
86+
f x1 : f x2 : f x3 : startUnrolledMap (n - 1) xs
87+
startUnrolledMap n (x1 : x2 : xs) =
88+
f x1 : f x2 : startUnrolledMap (n - 1) xs
89+
startUnrolledMap n (x : xs) = f x : startUnrolledMap (n - 1) xs
90+
91+
startUnrolledMap _ Nil = Nil
92+
93+
chunkedRevMap :: List a -> List b
94+
chunkedRevMap = go Nil
95+
where
96+
go :: List (List a) -> List a -> List b
97+
go chunksAcc chunk@(x1 : x2 : x3 : x4 : x5 : xs) =
98+
go (chunk : chunksAcc) xs
99+
go chunksAcc finalChunk =
100+
reverseUnrolledMap chunksAcc $ startUnrolledMap 0 finalChunk
101+
102+
reverseUnrolledMap :: List (List a) -> List b -> List b
103+
reverseUnrolledMap ((x1 : x2 : x3 : _) : cs) acc =
104+
reverseUnrolledMap cs (f x1 : f x2 : f x3 : acc)
105+
-- if we pattern match on Nil, we need a Partial constraint,
106+
-- which kills TCO
107+
reverseUnrolledMap _ acc = acc
69108

70109
instance foldableList :: Foldable List where
71110
foldr f b = foldl (flip f) b <<< rev Nil

0 commit comments

Comments
 (0)