Skip to content

Commit 39bb1c3

Browse files
committed
List Functor: mix unrolled and reverse map
Addresses #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. Benchmarked on 2017 Macbook Pro, 2.3 GHz Intel Core i5, 8 GB RAM. macOS Sierra 10.12.6 node v8.9.1
1 parent 8d3f8e5 commit 39bb1c3

File tree

1 file changed

+46
-2
lines changed

1 file changed

+46
-2
lines changed

src/Data/List/Types.purs

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
module Data.List.Types where
1+
module Data.List.Types
2+
( List(..)
3+
, (:)
4+
, NonEmptyList(..)
5+
) where
26

37
import Prelude
48

@@ -67,7 +71,47 @@ instance monoidList :: Monoid (List a) where
6771
mempty = Nil
6872

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

72116
instance functorWithIndexList :: FunctorWithIndex Int List where
73117
mapWithIndex f = foldrWithIndex (\i x acc -> f i x : acc) Nil

0 commit comments

Comments
 (0)