-
Notifications
You must be signed in to change notification settings - Fork 9
feat: indexable Blobs
#424
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Realizing this might be confusing with contextual dot notation...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, you mean the missing parens?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
See dfinity/motoko#5018.
TODO:
base!atmethodgetmethod