Conversation
https://github.com/dfinity/motoko/pull/5018/files TODO: - [ ] also do this for `base`! - [ ] `index` method - [ ] test
|
✨ Documentation preview for bda9424:
|
Benchmark Resultsbench/ArrayBuilding.bench.mo
|
| 1000 | 100000 | 1000000 | |
|---|---|---|---|
| List | 542_157 |
47_994_641 |
475_069_697 |
| Buffer | 342_007 |
33_903_437 |
339_003_652 |
| pure/List | 302_137 |
30_003_592 |
300_055_974 |
| VarArray ?T | 180_528 |
17_802_958 |
178_003_173 |
| VarArray T | 160_815 |
15_803_245 |
158_003_460 |
| Array (baseline) | 42_697 |
4_003_127 |
40_003_342 |
Heap
| 1000 | 100000 | 1000000 | |
|---|---|---|---|
| List | 272 B |
272 B |
272 B |
| Buffer | 272 B |
272 B |
272 B |
| pure/List | 272 B |
272 B |
272 B |
| VarArray ?T | 272 B |
272 B |
272 B |
| VarArray T | 272 B |
272 B |
272 B |
| Array (baseline) | 272 B |
272 B |
272 B |
Garbage Collection
| 1000 | 100000 | 1000000 | |
|---|---|---|---|
| List | 9.96 KiB |
797.46 KiB |
7.67 MiB |
| Buffer | 8.71 KiB |
782.15 KiB |
7.63 MiB |
| pure/List | 19.95 KiB |
1.91 MiB |
19.07 MiB |
| VarArray ?T | 8.24 KiB |
781.68 KiB |
7.63 MiB |
| VarArray T | 8.23 KiB |
781.67 KiB |
7.63 MiB |
| Array (baseline) | 4.3 KiB |
391.02 KiB |
3.82 MiB |
bench/FromIters.bench.mo $({\color{gray}0\%})$
Benchmarking the fromIter functions
Columns describe the number of elements in the input iter.
Instructions:
Heap:
Stable Memory:
Garbage Collection:
Instructions
| 100 | 10_000 | 100_000 | |
|---|---|---|---|
| Array.fromIter | 48_764 |
4_712_025 |
47_103_135 |
| List.fromIter | 31_698 |
3_061_541 |
30_603_553 |
| List.fromIter . Iter.reverse | 50_297 |
4_832_563 |
48_305_477 |
Heap
| 100 | 10_000 | 100_000 | |
|---|---|---|---|
| Array.fromIter | 272 B |
272 B |
272 B |
| List.fromIter | 272 B |
272 B |
272 B |
| List.fromIter . Iter.reverse | 272 B |
272 B |
272 B |
Garbage Collection
| 100 | 10_000 | 100_000 | |
|---|---|---|---|
| Array.fromIter | 2.76 KiB |
234.79 KiB |
2.29 MiB |
| List.fromIter | 3.51 KiB |
312.88 KiB |
3.05 MiB |
| List.fromIter . Iter.reverse | 5.11 KiB |
469.17 KiB |
4.58 MiB |
bench/ListBufferNewArray.bench.mo $({\color{green}-6.13\%})$
List vs. Buffer for creating known-size arrays
Performance comparison between List and Buffer for creating a new array.
Instructions:
Heap:
Stable Memory:
Garbage Collection:
Instructions
| 0 (baseline) | 1 | 5 | 10 | 100 (for loop) | |
|---|---|---|---|---|---|
| List | 1_367 |
2_687 |
8_667 |
13_462 |
73_164 |
| pure/List | 1_247 |
1_355 |
2_439 |
3_801 |
31_868 |
| Buffer | 2_119 |
2_271 |
3_518 |
5_085 |
36_640 |
Heap
| 0 (baseline) | 1 | 5 | 10 | 100 (for loop) | |
|---|---|---|---|---|---|
| List | 272 B |
272 B |
272 B |
272 B |
272 B |
| pure/List | 272 B |
272 B |
272 B |
272 B |
272 B |
| Buffer | 272 B |
272 B |
272 B |
272 B |
272 B |
Garbage Collection
| 0 (baseline) | 1 | 5 | 10 | 100 (for loop) | |
|---|---|---|---|---|---|
| List | 476 B |
516 B |
676 B |
784 B |
1.84 KiB |
| pure/List | 360 B |
380 B |
460 B |
560 B |
2.3 KiB |
| Buffer | 856 B |
864 B |
896 B |
936 B |
1.62 KiB |
bench/PriorityQueues.bench.mo $({\color{green}-2.68\%})$
Different priority queue implementations
_Compare the performance of the following priority queue implementations:
-
PriorityQueue: Binary heap implementation overList. -
PriorityQueueSet: Wrapper overSet<(T, Nat)>._
Instructions:
Heap:
Stable Memory:
Garbage Collection:
Instructions
| A) PriorityQueue | B) PriorityQueueSet | |
|---|---|---|
| 1.) 100000 operations (push:pop = 1:1) | 568_913_057 |
522_729_861 |
| 2.) 100000 operations (push:pop = 2:1) | 707_495_424 |
809_693_415 |
| 3.) 100000 operations (push:pop = 10:1) | 336_409_578 |
873_181_028 |
| 4.) 100000 operations (only push) | 176_982_954 |
886_824_792 |
| 5.) 50000 pushes, then 50000 pops | 745_226_615 |
961_776_534 |
| 6.) 50000 pushes, then 25000 "pop;push"es | 504_254_228 |
922_137_111 |
Heap
| A) PriorityQueue | B) PriorityQueueSet | |
|---|---|---|
| 1.) 100000 operations (push:pop = 1:1) | 272 B |
272 B |
| 2.) 100000 operations (push:pop = 2:1) | 272 B |
272 B |
| 3.) 100000 operations (push:pop = 10:1) | 272 B |
272 B |
| 4.) 100000 operations (only push) | 272 B |
272 B |
| 5.) 50000 pushes, then 50000 pops | 272 B |
272 B |
| 6.) 50000 pushes, then 25000 "pop;push"es | 272 B |
272 B |
Garbage Collection
| A) PriorityQueue | B) PriorityQueueSet | |
|---|---|---|
| 1.) 100000 operations (push:pop = 1:1) | 15.07 MiB |
17.43 MiB |
| 2.) 100000 operations (push:pop = 2:1) | 19.73 MiB |
19.32 MiB |
| 3.) 100000 operations (push:pop = 10:1) | 8.67 MiB |
12.64 MiB |
| 4.) 100000 operations (only push) | 3.87 MiB |
9.96 MiB |
| 5.) 50000 pushes, then 50000 pops | 22.03 MiB |
26.2 MiB |
| 6.) 50000 pushes, then 25000 "pop;push"es | 14.22 MiB |
18.44 MiB |
bench/PureListStackSafety.bench.mo $({\color{gray}0\%})$
List Stack safety
Check stack-safety of the following pure/List-related functions.
Instructions:
Heap:
Stable Memory:
Garbage Collection:
Instructions
| pure/List.split | 24_602_524 |
| pure/List.all | 7_901_014 |
| pure/List.any | 8_001_390 |
| pure/List.map | 23_103_767 |
| pure/List.filter | 21_104_188 |
| pure/List.filterMap | 27_404_742 |
| pure/List.partition | 21_304_994 |
| pure/List.join | 33_105_326 |
| pure/List.flatten | 24_805_667 |
| pure/List.take | 24_605_664 |
| pure/List.drop | 9_904_119 |
| pure/List.foldRight | 19_105_768 |
| pure/List.merge | 31_808_584 |
| pure/List.chunks | 51_510_344 |
| pure/Queue | 142_662_505 |
Heap
| pure/List.split | 272 B |
| pure/List.all | 272 B |
| pure/List.any | 272 B |
| pure/List.map | 272 B |
| pure/List.filter | 272 B |
| pure/List.filterMap | 272 B |
| pure/List.partition | 272 B |
| pure/List.join | 272 B |
| pure/List.flatten | 272 B |
| pure/List.take | 272 B |
| pure/List.drop | 272 B |
| pure/List.foldRight | 272 B |
| pure/List.merge | 272 B |
| pure/List.chunks | 272 B |
| pure/Queue | 272 B |
Garbage Collection
| pure/List.split | 3.05 MiB |
| pure/List.all | 328 B |
| pure/List.any | 328 B |
| pure/List.map | 3.05 MiB |
| pure/List.filter | 3.05 MiB |
| pure/List.filterMap | 3.05 MiB |
| pure/List.partition | 3.05 MiB |
| pure/List.join | 3.05 MiB |
| pure/List.flatten | 3.05 MiB |
| pure/List.take | 3.05 MiB |
| pure/List.drop | 328 B |
| pure/List.foldRight | 1.53 MiB |
| pure/List.merge | 4.58 MiB |
| pure/List.chunks | 7.63 MiB |
| pure/Queue | 18.31 MiB |
bench/Queues.bench.mo $({\color{gray}0\%})$
Different queue implementations
Compare the performance of the following queue implementations:
-
pure/Queue: The default immutable double-ended queue implementation.- Pros: Good amortized performance, meaning that the average cost of operations is low
O(1). - Cons: In worst case, an operation can take
O(size)time rebuilding the queue as demonstrated in thePop front 2 elementsscenario.
- Pros: Good amortized performance, meaning that the average cost of operations is low
-
pure/RealTimeQueue- Pros: Every operation is guaranteed to take at most
O(1)time and space. - Cons: Poor amortized performance: Instruction cost is on average 3x for pop and 8x for push compared to
pure/Queue.
- Pros: Every operation is guaranteed to take at most
- mutable
Queue- Pros: Also
O(1)guarantees with a lower constant factor thanpure/RealTimeQueue. Amortized performance is comparable topure/Queue. - Cons: It is mutable and cannot be used in
sharedtypes (not shareable).
- Pros: Also
Instructions:
Heap:
Stable Memory:
Garbage Collection:
Instructions
| pure/Queue | pure/RealTimeQueue | mutable Queue | |
|---|---|---|---|
| Initialize with 2 elements | 3_092 |
2_304 |
3_040 |
| Push 500 elements | 90_713 |
744_219 |
219_284 |
| Pop front 2 elements | 86_966 |
4_446 |
3_847 |
| Pop 150 front&back | 92_095 |
304_908 |
124_581 |
Heap
| pure/Queue | pure/RealTimeQueue | mutable Queue | |
|---|---|---|---|
| Initialize with 2 elements | 324 B |
300 B |
352 B |
| Push 500 elements | 8.08 KiB |
8.17 KiB |
19.8 KiB |
| Pop front 2 elements | 240 B |
240 B |
192 B |
| Pop 150 front&back | -4.42 KiB |
-492 B |
-11.45 KiB |
Garbage Collection
| pure/Queue | pure/RealTimeQueue | mutable Queue | |
|---|---|---|---|
| Initialize with 2 elements | 508 B |
444 B |
456 B |
| Push 500 elements | 10.1 KiB |
137.84 KiB |
344 B |
| Pop front 2 elements | 12.19 KiB |
528 B |
424 B |
| Pop 150 front&back | 15.61 KiB |
49.66 KiB |
12.1 KiB |
Note: Renamed benchmarks cannot be compared. Refer to the current baseline for manual comparison.
c713824 to
7ff6663
Compare
55ce12f to
fb3adce
Compare
| /// ``` | ||
| /// | ||
| /// Runtime: `O(1)` | ||
| public func at(blob : Blob, index : Nat) : Nat8 = blob.get index; |
There was a problem hiding this comment.
Realizing this might be confusing with contextual dot notation...
There was a problem hiding this comment.
Oh, you mean the missing parens?
There was a problem hiding this comment.
Thinking of the discrepancy where blob.get(i) returns a Nat8 whereas Blob.get(blob, i) would return an optional ?Nat8.
Not sure if there's much we can do about this aside from a breaking change to the compiler or using a different convention for the Blob module compared to List (which might confuse LLMs and humans).
There was a problem hiding this comment.
Oh, I get it now. But the same situation probably for Text if that provides indexing at all. That wouldn't be O(1), though.
There was a problem hiding this comment.
Double-checked that Text currently doesn't provide indexing. Maybe we could change blob.get(i) to return an optional as part of Motoko 1.0? This seems like a more useful design, since the current behavior is also available through blob[i].
There was a problem hiding this comment.
Makes sense. But then we should change all .get() accessors (e.g. array). Also we have to be careful to not suddenly generate garbage...
There was a problem hiding this comment.
This should be ok due to the change causing type checking errors that give devs a chance to update their code before compiling again.
See caffeinelabs/motoko#5018.
TODO:
base!atmethodgetmethod