Closed chenyan-dfinity closed 9 months ago
Note The flamegraph link only works after you merge. Unchanged benchmarks are omitted.
Measure different collection libraries written in both Motoko and Rust.
The library names with _rs
suffix are written in Rust; the rest are written in Motoko.
The _stable
and _stable_rs
suffix represents that the library directly writes the state to stable memory using Region
in Motoko and ic-stable-stuctures
in Rust.
We use the same random number generator with fixed seed to ensure that all collections contain the same elements, and the queries are exactly the same. Below we explain the measurements of each column in the table:
rts_max_heap_size
after generate
call; For Rust, it reports the Wasm's memory page * 32Kb.Note
- The Candid interface of the benchmark is minimal, therefore the serialization cost is negligible in this measurement.
- Due to the instrumentation overhead and cycle limit, we cannot profile computations with very large collections.
- The
upgrade
column uses Candid for serializing stable data. In Rust, you may get better cycle cost by using a different serialization format. Another slowdown in Rust is thatic-stable-structures
tends to be slower than the region memory in Motoko.- Different library has different ways for persisting data during upgrades, there are mainly three categories:
- Use stable variable directly in Motoko:
zhenya_hashmap
,btree
,vector
- Expose and serialize external state (
share/unshare
in Motoko,candid::Encode
in Rust):rbtree
,heap
,btreemap_rs
,hashmap_rs
,heap_rs
,vector_rs
- Use pre/post-upgrade hooks to convert data into an array:
hashmap
,splay
,triemap
,buffer
,imrc_hashmap_rs
- The stable benchmarks are much more expensive than their non-stable counterpart, because the stable memory API is much more expensive. The benefit is that they get fast upgrade. The upgrade still needs to parse the metadata when initializing the upgraded Wasm module.
hashmap
uses amortized data structure. When the initial capacity is reached, it has to copy the whole array, thus the cost ofbatch_put 50
is much higher than other data structures.btree
comes from mops.one/stableheapbtreemap.zhenya_hashmap
comes from mops.one/map.vector
comes from mops.one/vector. Compare withbuffer
,put
has better worst case time and space complexity ($O(\sqrt{n})$ vs $O(n)$);get
has a slightly larger constant overhead.hashmap_rs
uses thefxhash
crate, which is the same asstd::collections::HashMap
, but with a deterministic hasher. This ensures reproducible result.imrc_hashmap_rs
uses theim-rc
crate, which is the immutable version hashmap in Rust.
binary_size | generate 1m | max mem | batch_get 50 | batch_put 50 | batch_remove 50 | upgrade | |
---|---|---|---|---|---|---|---|
hashmap | 189_608 | 8_370_836_596 | 61_987_852 | 344_911 | 6_593_214_552 | 371_223 | 11_026_879_813 |
triemap | 195_507 | 13_855_038_807 | 74_216_172 | 254_589 | 661_468 | 650_820 | 15_817_661_582 |
rbtree | 186_562 | 7_127_318_517 | 57_996_060 | 114_300 | 318_352 | 328_277 | 7_169_325_607 |
splay | 190_525 | 13_247_297_529 | 53_995_996 | 628_661 | 661_579 | 921_933 | 4_567_871_575 |
btree | 229_870 | 10_266_014_811 | 31_104_012 | 353_622 | 482_125 | 533_935 | 3_134_168_915 |
zhenya_hashmap | 189_285 | 2_570_554_540 | 22_773_100 | 60_196 | 70_137 | 82_453 | 3_305_550_277 |
btreemap_rs | 537_393 | 1_793_333_047 | 27_590_656 | 75_328 | 125_166 | 86_260 | 2_937_041_107 |
imrc_hashmap_rs | 542_882 | 2_584_501_850 | 244_973_568 | 37_762 | 178_926 | 115_385 | 5_796_587_958 |
hashmap_rs | 529_458 | 439_248_112 | 73_138_176 | 21_501 | 26_711 | 25_024 | 1_298_646_667 |
binary_size | heapify 1m | max mem | pop_min 50 | put 50 | pop_min 50 | upgrade | |
---|---|---|---|---|---|---|---|
heap | 167_497 | 5_697_880_501 | 29_995_956 | 621_338 | 228_674 | 592_198 | 3_309_807_456 |
heap_rs | 525_853 | 139_669_830 | 18_284_544 | 57_419 | 23_051 | 57_545 | 510_960_192 |
binary_size | generate 5k | max mem | batch_get 500 | batch_put 500 | batch_remove 500 | upgrade | |
---|---|---|---|---|---|---|---|
buffer | 173_906 | 2_570_947 | 65_644 | 95_490 | 800_452 | 170_490 | 3_061_307 |
vector | 172_249 | 1_920_997 | 24_580 | 126_114 | 183_385 | 175_981 | 4_695_522 |
vec_rs | 520_881 | 289_040 | 1_376_256 | 17_251 | 30_571 | 23_331 | 3_161_017 |
binary_size | generate 50k | max mem | batch_get 50 | batch_put 50 | batch_remove 50 | upgrade | |
---|---|---|---|---|---|---|---|
btreemap_rs | 537_393 | 76_200_333 | 2_555_904 | 64_886 | 97_044 | 85_272 | 126_265_270 |
btreemap_stable_rs | 543_609 | 4_561_985_735 | 2_031_616 | 2_707_064 | 5_026_642 | 8_594_683 | 729_311 |
heap_rs | 525_853 | 7_051_730 | 2_293_760 | 49_928 | 23_299 | 49_894 | 26_768_703 |
heap_stable_rs | 506_559 | 271_553_517 | 458_752 | 2_294_851 | 238_596 | 2_277_771 | 729_317 |
vec_rs | 520_881 | 3_079_382 | 2_293_760 | 17_251 | 18_421 | 17_719 | 24_671_551 |
vec_stable_rs | 503_829 | 63_394_912 | 458_752 | 62_491 | 79_685 | 81_633 | 729_320 |
Environment
- dfx 0.16.1
- Motoko compiler 0.10.4 (source js20w7g2-ysgfrqd0-1cmy11nb-3wdy9y1k)
- rustc 1.75.0 (82e1608df 2023-12-21)
- ic-repl 0.6.2
- ic-wasm 0.7.0
Cryptographic libraries
Measure different cryptographic libraries written in both Motoko and Rust.
SHA-256/SHA-512
. Compute the hash of a 1M Wasm binary.account_id
. Compute the ledger account id from principal, based on SHA-224.neuron_id
. Compute the NNS neuron id from principal, based on SHA-256.generate 10k
. Insert 10k 7-character word as both key and value into the certified map.max mem
. For Motoko, it reports rts_max_heap_size
after generate
call; For Rust, it reports the Wasm's memory page * 32Kb.inc
. Increment a counter and insert the counter value into the map.witness
. Generate the root hash and a witness for the counter.upgrade
. Upgrade the canister with the same Wasm. In Motoko, we use stable variable. In Rust, we convert the tree to a vector before serialization.binary_size | SHA-256 | SHA-512 | account_id | neuron_id | |
---|---|---|---|---|---|
Motoko | 196_492 | 273_037_090 | 259_835_230 | 34_373 | 24_897 |
Rust | 537_397 | 82_787_911 | 56_792_991 | 47_914 | 50_388 |
binary_size | generate 10k | max mem | inc | witness | upgrade | |
---|---|---|---|---|---|---|
Motoko | 245_183 | 4_763_349_709 | 3_430_044 | 565_117 | 402_073 | 274_276_383 |
Rust | 565_792 | 6_409_147_805 | 2_228_224 | 1_019_959 | 303_897 | 6_019_483_730 |
Environment
- dfx 0.16.1
- Motoko compiler 0.10.4 (source js20w7g2-ysgfrqd0-1cmy11nb-3wdy9y1k)
- rustc 1.75.0 (82e1608df 2023-12-21)
- ic-repl 0.6.2
- ic-wasm 0.7.0
Sample Dapps
Measure the performance of some typical dapps:
heartbeat
disabled to make profiling easier. We have a separate benchmark to measure heartbeat performance.Note
- The cost difference is mainly due to the Candid serialization cost.
- Motoko statically compiles/specializes the serialization code for each method, whereas in Rust, we use
serde
to dynamically deserialize data based on data on the wire.- We could improve the performance on the Rust side by using parser combinators. But it is a challenge to maintain the ergonomics provided by
serde
.- For real-world applications, we tend to send small data for each endpoint, which makes the Candid overhead in Rust tolerable.
binary_size | init | transfer_token | submit_proposal | vote_proposal | upgrade | |
---|---|---|---|---|---|---|
Motoko | 275_458 | 510_765 | 22_312 | 18_606 | 19_662 | 157_656 |
Rust | 849_921 | 599_916 | 99_156 | 123_702 | 136_655 | 1_799_828 |
binary_size | init | mint_token | transfer_token | upgrade | |
---|---|---|---|---|---|
Motoko | 222_607 | 481_158 | 29_810 | 8_776 | 89_459 |
Rust | 869_104 | 236_542 | 368_044 | 91_941 | 1_999_207 |
Environment
- dfx 0.16.1
- Motoko compiler 0.10.4 (source js20w7g2-ysgfrqd0-1cmy11nb-3wdy9y1k)
- rustc 1.75.0 (82e1608df 2023-12-21)
- ic-repl 0.6.2
- ic-wasm 0.7.0
Heartbeat / Timer
Measure the cost of empty heartbeat and timer job.
setTimer
measures both the setTimer(0)
method and the execution of empty job.setTimer
and canister_global_timer
function. If it's not there, we may need to adjust the script.binary_size | heartbeat | |
---|---|---|
Motoko | 137_899 | 19_511 |
Rust | 23_637 | 480 |
binary_size | setTimer | cancelTimer | |
---|---|---|---|
Motoko | 146_290 | 51_655 | 4_610 |
Rust | 487_585 | 68_173 | 11_184 |
Environment
- dfx 0.16.1
- Motoko compiler 0.10.4 (source js20w7g2-ysgfrqd0-1cmy11nb-3wdy9y1k)
- rustc 1.75.0 (82e1608df 2023-12-21)
- ic-repl 0.6.2
- ic-wasm 0.7.0
Motoko Specific Benchmarks
Measure various features only available in Motoko.
Garbage Collection. Measure Motoko garbage collection cost using the Triemap benchmark. The max mem column reports rts_max_heap_size
after generate
call. The cycle cost numbers reported here are garbage collection cost only. Some flamegraphs are truncated due to the 2M log size limit. The dfx
/ic-wasm
optimizer is disabled for the garbage collection test cases due to how the optimizer affects function names, making profiling trickier.
generate
will trigger the copying GC. The rest of the methods will not trigger GC.--force-gc --copying-gc
.--force-gc --compacting-gc
.--force-gc --generational-gc
.--force-gc --incremental-gc
.Actor class. Measure the cost of spawning actor class, using the Actor classes example.
generate 700k | max mem | batch_get 50 | batch_put 50 | batch_remove 50 | |
---|---|---|---|---|---|
default | 1_171_395_786 | 51_991_392 | 119 | 119 | 119 |
copying | 1_171_395_668 | 51_991_392 | 1_171_104_136 | 1_171_195_696 | 1_171_106_949 |
compacting | 1_672_114_659 | 51_991_392 | 1_290_052_154 | 1_533_417_828 | 1_564_512_242 |
generational | 2_529_073_372 | 51_999_856 | 999_515_201 | 1_232_756 | 1_103_869 |
incremental | 29_503_170 | 985_890_644 | 477_971_911 | 493_638_503 | 1_092_284_316 |
binary size | put new bucket | put existing bucket | get | |
---|---|---|---|---|
Map | 300_186 | 816_032 | 16_099 | 16_644 |
Environment
- dfx 0.16.1
- Motoko compiler 0.10.4 (source js20w7g2-ysgfrqd0-1cmy11nb-3wdy9y1k)
- rustc 1.75.0 (82e1608df 2023-12-21)
- ic-repl 0.6.2
- ic-wasm 0.7.0
Publisher & Subscriber
Measure the cost of inter-canister calls from the Publisher & Subscriber example.
pub_binary_size | sub_binary_size | subscribe_caller | subscribe_callee | publish_caller | publish_callee | |
---|---|---|---|---|---|---|
Motoko | 161_935 | 146_225 | 28_593 | 11_963 | 22_864 | 6_430 |
Rust | 519_866 | 570_028 | 68_903 | 42_634 | 92_131 | 51_818 |
Environment
- dfx 0.16.1
- Motoko compiler 0.10.4 (source js20w7g2-ysgfrqd0-1cmy11nb-3wdy9y1k)
- rustc 1.75.0 (82e1608df 2023-12-21)
- ic-repl 0.6.2
- ic-wasm 0.7.0
Map
Priority queue
Growable array
Stable structures
Statistics
SHA-2
Certified map
Statistics
Basic DAO
DIP721 NFT
Statistics
Heartbeat
Timer
Statistics
Garbage Collection
Actor class
Statistics
Publisher & Subscriber
Statistics
binary_size: 6.75% [-1.85%, 15.35%]
max_mem: no change
cycles: -0.55% [-0.68%, -0.42%]
Overall Statistics
binary_size: 7.31% [5.35%, 9.26%]
max_mem: 1.78% [0.53%, 3.02%]
cycles: 1.06% [-2.36%, 4.47%]