Skip to content

Commit a167187

Browse files
alfonsogarciacaroncave
authored andcommitted
Prevent allocations
1 parent 0cd621d commit a167187

File tree

1 file changed

+85
-91
lines changed

1 file changed

+85
-91
lines changed

src/fable-library/List.fs

Lines changed: 85 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,31 @@ module SR =
1212
let listsHadDifferentLengths = "The lists had different lengths."
1313
let notEnoughElements = "The input sequence has an insufficient number of elements."
1414

15+
[<Emit("new Array($0)")>]
16+
let private allocate (i: int): ResizeArray<'T> = jsNative
17+
1518
// [<Struct>]
16-
[<CustomEquality; CustomComparison>]
19+
// [<CustomEquality; CustomComparison>]
1720
[<CompiledName("FSharpList")>]
18-
type ResizeList<'T> =
19-
{ Count: int; Values: ResizeArray<'T> }
21+
type ResizeList<'T>(count, values) =
22+
member _.Count: int = count
23+
member _.Values: ResizeArray<'T> = values
2024

21-
member inline internal xs.Add(x: 'T) =
25+
member internal xs.Add(x: 'T) =
2226
let values =
2327
if xs.Count = xs.Values.Count
2428
then xs.Values
2529
else xs.Values.GetRange(0, xs.Count)
2630
values.Add(x)
27-
{ Count = values.Count; Values = values }
31+
ResizeList<'T>.NewList(values)
2832

29-
member inline internal xs.Reverse() =
30-
let values = xs.Values.GetRange(0, xs.Count)
31-
values.Reverse()
32-
{ Count = values.Count; Values = values }
33+
member internal xs.Reverse() =
34+
let values = allocate xs.Count
35+
let mutable j = 0
36+
for i = xs.Count - 1 downto 0 do
37+
values.[j] <- xs.Values.[i]
38+
j <- j + 1
39+
ResizeList<'T>.NewList(values)
3340

3441
// This is a destructive internal optimization that
3542
// can only be performed on newly constructed lists.
@@ -38,30 +45,31 @@ type ResizeList<'T> =
3845
xs
3946

4047
static member inline Singleton(x: 'T) =
41-
let values = ResizeArray<'T>()
42-
values.Add(x)
43-
{ Count = 1; Values = values }
48+
ResizeList<'T>.NewList(ResizeArray [|x|])
4449

4550
static member inline NewList (values: ResizeArray<'T>) =
46-
{ Count = values.Count; Values = values }
51+
ResizeList(values.Count, values)
52+
53+
static member inline NewList (count, values) =
54+
ResizeList(count, values)
4755

48-
static member inline Empty =
49-
{ Count = 0; Values = ResizeArray<'T>() }
56+
static member inline Empty: ResizeList<'T> =
57+
ResizeList<'T>.NewList(ResizeArray())
5058

5159
static member inline Cons (x: 'T, xs: 'T list) = xs.Add(x)
5260

5361
member inline xs.IsEmpty = xs.Count <= 0
5462

5563
member inline xs.Length = xs.Count
5664

57-
member inline xs.Head =
65+
member xs.Head =
5866
if xs.Count > 0
5967
then xs.Values.[xs.Count - 1]
6068
else invalidArg "list" SR.inputListWasEmpty
6169

62-
member inline xs.Tail =
70+
member xs.Tail =
6371
if xs.Count > 0
64-
then { Count = xs.Count - 1; Values = xs.Values }
72+
then ResizeList<'T>.NewList(xs.Count - 1, xs.Values)
6573
else invalidArg "list" SR.inputListWasEmpty
6674

6775
member inline xs.Item with get (index: int) =
@@ -121,7 +129,7 @@ and 'T list = ResizeList<'T>
121129

122130
let inline indexNotFound() = raise (System.Collections.Generic.KeyNotFoundException(SR.keyNotFoundAlt))
123131

124-
let newList values = ResizeList.NewList (values)
132+
let newList values = ResizeList<'T>.NewList (values)
125133

126134
let empty () = ResizeList.Empty
127135

@@ -174,22 +182,24 @@ let foldBack (folder: 'T -> 'acc -> 'acc) (xs: 'T list) (state: 'acc) =
174182
let reverse (xs: 'a list) =
175183
xs.Reverse()
176184

177-
let inline reverseInPlace (xs: 'a list) =
178-
xs.ReverseInPlace()
185+
let ofResizeArrayInPlace (xs: ResizeArray<'a>) =
186+
xs.Reverse()
187+
ResizeList.NewList xs
179188

180189
let toSeq (xs: 'a list): 'a seq =
181190
xs :> System.Collections.Generic.IEnumerable<'a>
182191

183192
let ofSeq (xs: 'a seq): 'a list =
184193
// Seq.fold (fun acc x -> cons x acc) ResizeList.Empty xs
185-
// |> reverseInPlace
194+
// |> ofResizeArrayInPlace
186195
let values = ResizeArray(xs)
187196
values.Reverse()
188197
values |> newList
189198

190199
let concat (lists: seq<'a list>) =
191-
Seq.fold (fold (fun acc x -> cons x acc)) ResizeList.Empty lists
192-
|> reverseInPlace
200+
(ResizeArray(), lists)
201+
||> Seq.fold (fold (fun acc x -> acc.Add(x); acc))
202+
|> ofResizeArrayInPlace
193203

194204
let fold2 f (state: 'acc) (xs: 'a list) (ys: 'b list) =
195205
Seq.fold2 f state xs ys
@@ -200,9 +210,9 @@ let foldBack2 f (xs: 'a list) (ys: 'b list) (state: 'acc) =
200210
let unfold (gen: 'acc -> ('T * 'acc) option) (state: 'acc) =
201211
let rec loop st acc =
202212
match gen st with
203-
| None -> reverseInPlace acc
204-
| Some (x, st) -> loop st (cons x acc)
205-
loop state ResizeList.Empty
213+
| None -> ofResizeArrayInPlace acc
214+
| Some (x, st) -> acc.Add(x); loop st acc
215+
loop state (ResizeArray())
206216

207217
let scan f (state: 'acc) (xs: 'a list) =
208218
Seq.scan f state xs |> ofSeq
@@ -211,21 +221,24 @@ let scanBack f (xs: 'a list) (state: 'acc) =
211221
Seq.scanBack f xs state |> ofSeq
212222

213223
let append (xs: 'a list) (ys: 'a list) =
214-
// foldBack cons xs ys
215-
let mutable acc = ys
216-
for i = xs.Length - 1 downto 0 do
217-
acc <- cons xs.[i] acc
218-
acc
224+
let ylen = ys.Count
225+
let values = allocate (xs.Count + ys.Count)
226+
for i = xs.Count - 1 downto 0 do
227+
values.[i + ylen] <- xs.Values.[i]
228+
for i = ys.Count - 1 downto 0 do
229+
values.[i] <- ys.Values.[i]
230+
ResizeList<'a>.NewList(values)
219231

220232
let collect (f: 'a -> 'b list) (xs: 'a list) =
221233
Seq.collect f xs |> ofSeq
222234

223235
let mapIndexed (f: int -> 'a -> 'b) (xs: 'a list) =
224-
let rec loop i acc =
225-
if i < xs.Length
226-
then loop (i + 1) (cons (f i xs.[i]) acc)
227-
else reverseInPlace acc
228-
loop 0 ResizeList.Empty
236+
let values = allocate xs.Count
237+
let mutable j = 0
238+
for i = xs.Count - 1 downto 0 do
239+
values.[i] <- f j xs.Values.[i]
240+
j <- j + 1
241+
ResizeList<'b>.NewList(values)
229242

230243
let map (f: 'a -> 'b) (xs: 'a list) =
231244
mapIndexed (fun _i x -> f x) xs
@@ -243,11 +256,12 @@ let map3 f xs ys zs =
243256
Seq.map3 f xs ys zs |> ofSeq
244257

245258
let mapFold (f: 'S -> 'T -> 'R * 'S) s xs =
246-
let folder (nxs, fs) x =
259+
let folder (nxs: ResizeArray<_>, fs) x =
247260
let nx, fs = f fs x
248-
cons nx nxs, fs
249-
let nxs, s = fold folder (ResizeList.Empty, s) xs
250-
reverseInPlace nxs, s
261+
nxs.Add(nx)
262+
nxs, fs
263+
let nxs, s = fold folder (ResizeArray(), s) xs
264+
ofResizeArrayInPlace nxs, s
251265

252266
let mapFoldBack (f: 'T -> 'S -> 'R * 'S) xs s =
253267
mapFold (fun s v -> f v s) s (reverse xs)
@@ -265,10 +279,10 @@ let iterateIndexed2 f xs ys =
265279
fold2 (fun i x y -> f i x y; i + 1) 0 xs ys |> ignore
266280

267281
let ofArrayWithTail (xs: 'T[]) (tail: 'T list) =
268-
let mutable res = tail
282+
let values = tail.Values
269283
for i = xs.Length - 1 downto 0 do
270-
res <- cons xs.[i] res
271-
res
284+
values.Add(xs.[i])
285+
newList values
272286

273287
// let ofArray (xs: 'T[]) =
274288
// ofArrayWithTail xs ResizeList.Empty
@@ -369,53 +383,26 @@ let tryItem index (xs: 'a list) =
369383
else None
370384

371385
let filter f xs =
372-
fold (fun acc x ->
386+
(ResizeArray(), xs)
387+
||> fold (fun acc x ->
373388
if f x
374-
then cons x acc
375-
else acc) ResizeList.Empty xs
376-
|> reverseInPlace
389+
then acc.Add(x); acc
390+
else acc)
391+
|> ofResizeArrayInPlace
377392

393+
// TODO: Optimize this
378394
let partition f xs =
379395
fold (fun (lacc, racc) x ->
380396
if f x then cons x lacc, racc
381397
else lacc, cons x racc) (ResizeList.Empty, ResizeList.Empty) (reverse xs)
382398

383-
let tryItem n (xs: 'T list) =
384-
let rec loop i (xs: 'T list) =
385-
if xs.IsEmpty then None
386-
else
387-
if i = n then Some xs.Head
388-
else loop (i + 1) xs.Tail
389-
loop 0 xs
390-
391-
let item n (xs: 'T list) = xs.Item(n)
392-
393-
let filter f (xs: 'T list) =
394-
let root = List.Empty
395-
let folder (acc: 'T list) x =
396-
if f x then acc.AppendConsNoTail x else acc
397-
let node = fold folder root xs
398-
node.SetConsTail List.Empty
399-
root.Tail
400-
401-
let partition f (xs: 'T list) =
402-
let root1, root2 = List.Empty, List.Empty
403-
let folder (lacc: 'T list, racc: 'T list) x =
404-
if f x
405-
then lacc.AppendConsNoTail x, racc
406-
else lacc, racc.AppendConsNoTail x
407-
let node1, node2 = fold folder (root1, root2) xs
408-
node1.SetConsTail List.Empty
409-
node2.SetConsTail List.Empty
410-
root1.Tail, root2.Tail
411-
412-
let choose f (xs: 'T list) =
413-
let root = List.Empty
414-
let folder (acc: 'T list) x =
399+
let choose f xs =
400+
(ResizeArray(), xs)
401+
||> fold (fun acc x ->
415402
match f x with
416-
| Some y -> cons y acc
417-
| None -> acc) ResizeList.Empty xs
418-
|> reverseInPlace
403+
| Some y -> acc.Add(y); acc
404+
| None -> acc)
405+
|> ofResizeArrayInPlace
419406

420407
let contains (value: 'T) (xs: 'T list) ([<Inject>] eq: System.Collections.Generic.IEqualityComparer<'T>) =
421408
tryFindIndex (fun v -> eq.Equals (value, v)) xs |> Option.isSome
@@ -427,10 +414,12 @@ let except (itemsToExclude: seq<'t>) (xs: 't list) ([<Inject>] eq: System.Collec
427414
xs |> filter cached.Add
428415

429416
let initialize n f =
430-
let mutable res = ResizeList.Empty
431-
for i = 0 to n - 1 do
432-
res <- cons (f i) res
433-
res |> reverseInPlace
417+
let mutable j = 0
418+
let values = allocate n
419+
for i = n - 1 downto 0 do
420+
values.[i] <- f j
421+
j <- j + 1
422+
values |> newList
434423

435424
let replicate n x =
436425
initialize n (fun _ -> x)
@@ -458,6 +447,7 @@ let rec exists2 f xs ys =
458447
| x, y when x = y -> f (head xs) (head ys) || exists2 f (tail xs) (tail ys)
459448
| _ -> invalidArg "list2" SR.listsHadDifferentLengths
460449

450+
// TODO: Optimize this
461451
let unzip xs =
462452
foldBack (fun (x, y) (lacc, racc) -> cons x lacc, cons y racc) xs (ResizeList.Empty, ResizeList.Empty)
463453

@@ -473,7 +463,7 @@ let zip3 xs ys zs =
473463
let sortWith (comparison: 'T -> 'T -> int) (xs: 'T list): 'T list =
474464
let values = ResizeArray(xs)
475465
values.Sort(System.Comparison<_>(comparison)) // should be a stable sort in JS
476-
values |> newList |> reverseInPlace
466+
values |> ofResizeArrayInPlace
477467

478468
let sort (xs: 'T list) ([<Inject>] comparer: System.Collections.Generic.IComparer<'T>): 'T list =
479469
sortWith (fun x y -> comparer.Compare(x, y)) xs
@@ -545,8 +535,11 @@ let getSlice (startIndex: int option) (endIndex: int option) (xs: 'T list) =
545535
let startIndex = if startIndex < 0 then 0 else startIndex
546536
let endIndex = if endIndex >= xs.Length then xs.Length - 1 else endIndex
547537
// take (endIndex - startIndex + 1) (skip startIndex xs)
548-
let values = ResizeArray(endIndex - startIndex + 1)
549-
for i = endIndex downto startIndex do values.Add(xs.[i])
538+
let values = allocate (endIndex - startIndex + 1)
539+
let mutable j = 0
540+
for i = endIndex downto startIndex do
541+
values.[j] <- xs.[i]
542+
j <- j + 1
550543
values |> newList
551544

552545
let splitAt index (xs: 'T list) =
@@ -567,6 +560,7 @@ let exactlyOne (xs: 'T list) =
567560
| 0 -> invalidArg "list" SR.inputSequenceEmpty
568561
| _ -> invalidArg "list" SR.inputSequenceTooLong
569562

563+
// TODO: Optimize this
570564
let groupBy (projection: 'T -> 'Key) (xs: 'T list)([<Inject>] eq: System.Collections.Generic.IEqualityComparer<'Key>): ('Key * 'T list) list =
571565
let dict = System.Collections.Generic.Dictionary<'Key, 'T list>(eq)
572566
let mutable keys = ResizeList.Empty
@@ -579,7 +573,7 @@ let groupBy (projection: 'T -> 'Key) (xs: 'T list)([<Inject>] eq: System.Collect
579573
dict.Add(key, cons v ResizeList.Empty)
580574
keys <- cons key keys )
581575
let mutable result = ResizeList.Empty
582-
keys |> iterate (fun key -> result <- cons (key, reverseInPlace dict.[key]) result)
576+
keys |> iterate (fun key -> result <- cons (key, dict.[key].ReverseInPlace()) result)
583577
result
584578

585579
let countBy (projection: 'T -> 'Key) (xs: 'T list)([<Inject>] eq: System.Collections.Generic.IEqualityComparer<'Key>) =

0 commit comments

Comments
 (0)