Skip to content

Commit b300fec

Browse files
add stable benchmark (dfinity#92)
* add stable benchmark * fix * adjust to 800k * fix * 600k * fix * fix * vec_stable * heap_stable * readme
1 parent 9c6c2f2 commit b300fec

File tree

13 files changed

+314
-7
lines changed

13 files changed

+314
-7
lines changed

Cargo.lock

Lines changed: 30 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ members = [
33
"utils/rust",
44
"collections/rust/src/hashmap",
55
"collections/rust/src/btreemap",
6+
"collections/rust/src/btreemap_stable",
67
"collections/rust/src/heap",
8+
"collections/rust/src/heap_stable",
79
"collections/rust/src/imrc_hashmap",
810
"collections/rust/src/vector",
11+
"collections/rust/src/vector_stable",
912
"crypto/rust/src/sha",
1013
"crypto/rust/src/certified_map",
1114
"dapps/rust/dip721-nft",

collections/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
Measure different collection libraries written in both Motoko and Rust.
44
The library names with `_rs` suffix are written in Rust; the rest are written in Motoko.
5+
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.
56

67
We use the same random number generator with fixed seed to ensure that all collections contain
78
the same elements, and the queries are exactly the same. Below we explain the measurements of each column in the table:
@@ -11,7 +12,7 @@ the same elements, and the queries are exactly the same. Below we explain the me
1112
* batch_get 50. Find 50 elements from the collection.
1213
* batch_put 50. Insert 50 elements to the collection.
1314
* batch_remove 50. Remove 50 elements from the collection.
14-
* upgrade. Upgrade the canister with the same Wasm module. The map state is persisted by serializing and deserializing states into stable memory.
15+
* upgrade. Upgrade the canister with the same Wasm module. For non-stable benchmarks, the map state is persisted by serializing and deserializing states into stable memory. For stable benchmarks, the upgrade takes no cycles, as the state is already in the stable memory.
1516

1617
## **💎 Takeaways**
1718

@@ -29,6 +30,7 @@ the same elements, and the queries are exactly the same. Below we explain the me
2930
> + Use stable variable directly in Motoko: `zhenya_hashmap`, `btree`, `vector`
3031
> + Expose and serialize external state (`share/unshare` in Motoko, `candid::Encode` in Rust): `rbtree`, `heap`, `btreemap_rs`, `hashmap_rs`, `heap_rs`, `vector_rs`
3132
> + Use pre/post-upgrade hooks to convert data into an array: `hashmap`, `splay`, `triemap`, `buffer`, `imrc_hashmap_rs`
33+
> * 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 zero cost upgrade.
3234
> * `hashmap` uses amortized data structure. When the initial capacity is reached, it has to copy the whole array, thus the cost of `batch_put 50` is much higher than other data structures.
3335
> * `btree` comes from [mops.one/stableheapbtreemap](https://mops.one/stableheapbtreemap).
3436
> * `zhenya_hashmap` comes from [mops.one/map](https://mops.one/map).

collections/perf.sh

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,17 @@ let vector = wasm_profiling("motoko/.dfx/local/canisters/vector/vector.wasm", re
1313

1414
let hashmap_rs = wasm_profiling("rust/.dfx/local/canisters/hashmap/hashmap.wasm", record { start_page = 1 });
1515
let btreemap_rs = wasm_profiling("rust/.dfx/local/canisters/btreemap/btreemap.wasm", record { start_page = 1 });
16+
let btreemap_stable_rs = wasm_profiling("rust/.dfx/local/canisters/btreemap_stable/btreemap_stable.wasm", record { start_page = 1 });
1617
let heap_rs = wasm_profiling("rust/.dfx/local/canisters/heap/heap.wasm", record { start_page = 1 });
18+
let heap_stable_rs = wasm_profiling("rust/.dfx/local/canisters/heap_stable/heap_stable.wasm", record { start_page = 1 });
1719
let imrc_hashmap_rs = wasm_profiling("rust/.dfx/local/canisters/imrc_hashmap/imrc_hashmap.wasm", record { start_page = 1 });
1820
let vector_rs = wasm_profiling("rust/.dfx/local/canisters/vector/vector.wasm", record { start_page = 1 });
21+
let vector_stable_rs = wasm_profiling("rust/.dfx/local/canisters/vector_stable/vector_stable.wasm", record { start_page = 1 });
1922

2023
//let movm_rs = wasm_profiling("rust/.dfx/local/canisters/movm/movm.wasm");
2124
//let movm_dynamic_rs = wasm_profiling("rust/.dfx/local/canisters/movm_dynamic/movm_dynamic.wasm");
2225

2326
let file = "README.md";
24-
output(file, "\n## Map\n\n| |binary_size|generate 1m|max mem|batch_get 50|batch_put 50|batch_remove 50|upgrade|\n|--:|--:|--:|--:|--:|--:|--:|--:|\n");
2527

2628
function perf(wasm, title, init, batch) {
2729
let cid = install(wasm, encode (), null);
@@ -60,7 +62,7 @@ function perf(wasm, title, init, batch) {
6062

6163
let init_size = 1_000_000;
6264
let batch_size = 50;
63-
65+
output(file, "\n## Map\n\n| |binary_size|generate 1m|max mem|batch_get 50|batch_put 50|batch_remove 50|upgrade|\n|--:|--:|--:|--:|--:|--:|--:|--:|\n");
6466
perf(hashmap, "hashmap", init_size, batch_size);
6567
perf(triemap, "triemap", init_size, batch_size);
6668
perf(rbtree, "rbtree", init_size, batch_size);
@@ -82,6 +84,15 @@ perf(buffer, "buffer", init_size, batch_size);
8284
perf(vector, "vector", init_size, batch_size);
8385
perf(vector_rs, "vec_rs", init_size, batch_size);
8486

87+
let init_size = 50_000;
88+
let batch_size = 50;
89+
output(file, "\n## Stable structures\n\n| |binary_size|generate 50k|max mem|batch_get 50|batch_put 50|batch_remove 50|upgrade|\n|--:|--:|--:|--:|--:|--:|--:|--:|\n");
90+
perf(btreemap_rs, "btreemap_rs", init_size, batch_size);
91+
perf(btreemap_stable_rs, "btreemap_stable_rs", init_size, batch_size);
92+
perf(heap_rs, "heap_rs", init_size, batch_size);
93+
perf(heap_stable_rs, "heap_stable_rs", init_size, batch_size);
94+
perf(vector_rs, "vec_rs", init_size, batch_size);
95+
perf(vector_stable_rs, "vec_stable_rs", init_size, batch_size);
8596

8697
/*
8798
let movm_size = 10000;

collections/rust/dfx.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,21 @@
1111
"candid": "collection.did",
1212
"package": "btreemap"
1313
},
14+
"btreemap_stable": {
15+
"type": "rust",
16+
"candid": "collection.did",
17+
"package": "btreemap_stable"
18+
},
1419
"heap": {
1520
"type": "rust",
1621
"candid": "collection.did",
1722
"package": "heap"
1823
},
24+
"heap_stable": {
25+
"type": "rust",
26+
"candid": "collection.did",
27+
"package": "heap_stable"
28+
},
1929
"imrc_hashmap": {
2030
"type": "rust",
2131
"candid": "collection.did",
@@ -25,6 +35,11 @@
2535
"type": "rust",
2636
"candid": "collection.did",
2737
"package": "vector"
38+
},
39+
"vector_stable": {
40+
"type": "rust",
41+
"candid": "collection.did",
42+
"package": "vector_stable"
2843
}
2944
},
3045
"defaults": {
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "btreemap_stable"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[lib]
9+
crate-type = ["cdylib"]
10+
11+
[dependencies]
12+
candid.workspace = true
13+
ic-cdk.workspace = true
14+
ic-stable-structures.workspace = true
15+
utils = { path = "../../../../utils/rust" }
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
use ic_stable_structures::StableBTreeMap;
2+
use std::cell::RefCell;
3+
use utils::{Memory, Random};
4+
5+
thread_local! {
6+
static MAP: RefCell<StableBTreeMap<u64, u64, Memory>> = RefCell::new(StableBTreeMap::init(utils::get_upgrade_memory()));
7+
static RAND: RefCell<Random> = RefCell::new(Random::new(None, 42));
8+
}
9+
10+
#[ic_cdk::init]
11+
fn init() {
12+
utils::profiling_init();
13+
}
14+
15+
#[ic_cdk::update]
16+
fn generate(size: u32) {
17+
let rand = Random::new(Some(size), 1);
18+
let iter = rand.map(|x| (x, x));
19+
MAP.with(|map| {
20+
let mut map = map.borrow_mut();
21+
for (k, v) in iter {
22+
map.insert(k, v);
23+
}
24+
});
25+
}
26+
27+
#[ic_cdk::query]
28+
fn get_mem() -> (u128, u128, u128) {
29+
utils::get_upgrade_mem_size()
30+
}
31+
32+
#[ic_cdk::update]
33+
fn batch_get(n: u32) {
34+
MAP.with(|map| {
35+
let map = map.borrow();
36+
RAND.with(|rand| {
37+
let mut rand = rand.borrow_mut();
38+
for _ in 0..n {
39+
let k = rand.next().unwrap();
40+
map.get(&k);
41+
}
42+
})
43+
})
44+
}
45+
46+
#[ic_cdk::update]
47+
fn batch_put(n: u32) {
48+
MAP.with(|map| {
49+
let mut map = map.borrow_mut();
50+
RAND.with(|rand| {
51+
let mut rand = rand.borrow_mut();
52+
for _ in 0..n {
53+
let k = rand.next().unwrap();
54+
map.insert(k, k);
55+
}
56+
})
57+
})
58+
}
59+
60+
#[ic_cdk::update]
61+
fn batch_remove(n: u32) {
62+
let mut rand = Random::new(None, 1);
63+
MAP.with(|map| {
64+
let mut map = map.borrow_mut();
65+
for _ in 0..n {
66+
let k = rand.next().unwrap();
67+
map.remove(&k);
68+
}
69+
})
70+
}

collections/rust/src/heap/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,4 @@ crate-type = ["cdylib"]
1111
[dependencies]
1212
candid.workspace = true
1313
ic-cdk.workspace = true
14-
serde.workspace = true
1514
utils = { path = "../../../../utils/rust" }
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "heap_stable"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[lib]
9+
crate-type = ["cdylib"]
10+
11+
[dependencies]
12+
candid.workspace = true
13+
ic-cdk.workspace = true
14+
ic-stable-structures.workspace = true
15+
utils = { path = "../../../../utils/rust" }
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
use ic_stable_structures::StableMinHeap;
2+
use std::cell::RefCell;
3+
use std::cmp::Reverse;
4+
use utils::{Memory, Random};
5+
6+
thread_local! {
7+
static MAP: RefCell<StableMinHeap<Reverse<u64>, Memory>> = RefCell::new(StableMinHeap::init(utils::get_upgrade_memory()).unwrap());
8+
static RAND: RefCell<Random> = RefCell::new(Random::new(None, 42));
9+
}
10+
11+
#[ic_cdk::init]
12+
fn init() {
13+
utils::profiling_init();
14+
}
15+
16+
#[ic_cdk::update]
17+
fn generate(size: u32) {
18+
let rand = Random::new(Some(size), 1);
19+
let iter = rand.map(Reverse);
20+
MAP.with(|map| {
21+
let mut map = map.borrow_mut();
22+
for x in iter {
23+
map.push(&x).unwrap();
24+
}
25+
});
26+
}
27+
28+
#[ic_cdk::query]
29+
fn get_mem() -> (u128, u128, u128) {
30+
utils::get_upgrade_mem_size()
31+
}
32+
33+
#[ic_cdk::update]
34+
fn batch_get(n: u32) {
35+
MAP.with(|map| {
36+
let mut map = map.borrow_mut();
37+
for _ in 0..n {
38+
map.pop();
39+
}
40+
})
41+
}
42+
43+
#[ic_cdk::update]
44+
fn batch_put(n: u32) {
45+
MAP.with(|map| {
46+
let mut map = map.borrow_mut();
47+
RAND.with(|rand| {
48+
let mut rand = rand.borrow_mut();
49+
for _ in 0..n {
50+
let k = rand.next().unwrap();
51+
map.push(&Reverse(k)).unwrap();
52+
}
53+
})
54+
})
55+
}
56+
57+
#[ic_cdk::update]
58+
fn batch_remove(n: u32) {
59+
MAP.with(|map| {
60+
let mut map = map.borrow_mut();
61+
for _ in 0..n {
62+
map.pop();
63+
}
64+
})
65+
}

0 commit comments

Comments
 (0)