Skip to content

Commit fa8b70a

Browse files
authored
Benchmarking Compute Units (#6)
1 parent 6ee8744 commit fa8b70a

File tree

11 files changed

+352
-6
lines changed

11 files changed

+352
-6
lines changed

.github/workflows/main.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,38 @@ jobs:
109109
- name: Test Programs
110110
run: pnpm programs:test
111111

112+
bench_program_compute_units:
113+
name: Benchmark Program Compute Units
114+
runs-on: ubuntu-latest
115+
needs: build_programs # Cargo Bench won't build the SBPF binary...
116+
steps:
117+
- name: Git Checkout
118+
uses: actions/checkout@v4
119+
120+
- name: Setup Environment
121+
uses: ./.github/actions/setup
122+
with:
123+
cargo-cache-key: cargo-program-benches
124+
cargo-cache-fallback-key: cargo-programs
125+
solana: true
126+
127+
- name: Restore Program Builds
128+
uses: actions/cache/restore@v4
129+
with:
130+
path: ./**/*.so
131+
key: ${{ runner.os }}-builds-${{ github.sha }}
132+
133+
- name: Benchmark Compute Units
134+
run: pnpm programs:bench
135+
136+
- name: Check Working Directory
137+
run: |
138+
if [ -n "$(git status --porcelain)" ]; then
139+
test -z "$(git status --porcelain)"
140+
echo "CU usage has changed. Please run `cargo bench` and commit the new results.";
141+
exit 1;
142+
fi
143+
112144
## SKIP: IDL is hand-cranked here for now.
113145
##
114146
# generate_idls:

Cargo.lock

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"scripts": {
44
"programs:build": "zx ./scripts/program/build.mjs",
55
"programs:test": "zx ./scripts/program/test.mjs",
6+
"programs:bench": "zx ./scripts/program/bench.mjs",
67
"programs:clean": "zx ./scripts/program/clean.mjs",
78
"programs:format": "zx ./scripts/program/format.mjs",
89
"programs:lint": "zx ./scripts/program/lint.mjs",

program/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,12 @@ solana-program = "2.0.1"
2222

2323
[dev-dependencies]
2424
mollusk-svm = { version = "0.0.5", features = ["fuzz"] }
25+
mollusk-svm-bencher = "0.0.5"
2526
solana-sdk = "2.0.1"
2627

2728
[lib]
2829
crate-type = ["cdylib", "lib"]
30+
31+
[[bench]]
32+
name = "compute_units"
33+
harness = false

program/benches/compute_units.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#### Compute Units: 2024-10-23 12:46:27.264402 UTC
2+
3+
| Name | CUs | Delta |
4+
|------|------|-------|
5+
| config_small_init_0_keys | 581 | - new - |
6+
| config_small_init_1_keys | 1204 | - new - |
7+
| config_small_init_5_keys | 2799 | - new - |
8+
| config_small_init_10_keys | 4839 | - new - |
9+
| config_small_init_25_keys | 11591 | - new - |
10+
| config_small_init_37_keys | 16522 | - new - |
11+
| config_small_store_0_keys | 581 | - new - |
12+
| config_small_store_1_keys | 1458 | - new - |
13+
| config_small_store_5_keys | 3969 | - new - |
14+
| config_small_store_10_keys | 7154 | - new - |
15+
| config_small_store_25_keys | 17341 | - new - |
16+
| config_small_store_37_keys | 25020 | - new - |
17+
| config_medium_init_0_keys | 572 | - new - |
18+
| config_medium_init_1_keys | 1151 | - new - |
19+
| config_medium_init_5_keys | 2799 | - new - |
20+
| config_medium_init_10_keys | 4839 | - new - |
21+
| config_medium_init_25_keys | 11591 | - new - |
22+
| config_medium_init_37_keys | 16522 | - new - |
23+
| config_medium_store_0_keys | 572 | - new - |
24+
| config_medium_store_1_keys | 1405 | - new - |
25+
| config_medium_store_5_keys | 3969 | - new - |
26+
| config_medium_store_10_keys | 7154 | - new - |
27+
| config_medium_store_25_keys | 17341 | - new - |
28+
| config_medium_store_37_keys | 25020 | - new - |
29+
| config_large_init_0_keys | 693 | - new - |
30+
| config_large_init_1_keys | 1272 | - new - |
31+
| config_large_init_5_keys | 2920 | - new - |
32+
| config_large_init_10_keys | 4961 | - new - |
33+
| config_large_init_25_keys | 11715 | - new - |
34+
| config_large_init_37_keys | 16647 | - new - |
35+
| config_large_store_0_keys | 693 | - new - |
36+
| config_large_store_1_keys | 1526 | - new - |
37+
| config_large_store_5_keys | 4090 | - new - |
38+
| config_large_store_10_keys | 7276 | - new - |
39+
| config_large_store_25_keys | 17465 | - new - |
40+
| config_large_store_37_keys | 25145 | - new - |
41+

program/benches/compute_units.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//! Compute unit benchmark testing.
2+
3+
mod setup;
4+
5+
use {
6+
crate::setup::{BenchSetup, ConfigLarge, ConfigMedium, ConfigSmall},
7+
mollusk_svm::Mollusk,
8+
mollusk_svm_bencher::MolluskComputeUnitBencher,
9+
};
10+
11+
fn main() {
12+
std::env::set_var("SBF_OUT_DIR", "../target/deploy");
13+
let mollusk = Mollusk::new(&solana_config_program::id(), "solana_config_program");
14+
15+
MolluskComputeUnitBencher::new(mollusk)
16+
.bench(ConfigSmall::init(0).bench())
17+
.bench(ConfigSmall::init(1).bench())
18+
.bench(ConfigSmall::init(5).bench())
19+
.bench(ConfigSmall::init(10).bench())
20+
.bench(ConfigSmall::init(25).bench())
21+
.bench(ConfigSmall::init(37).bench())
22+
.bench(ConfigSmall::store(0).bench())
23+
.bench(ConfigSmall::store(1).bench())
24+
.bench(ConfigSmall::store(5).bench())
25+
.bench(ConfigSmall::store(10).bench())
26+
.bench(ConfigSmall::store(25).bench())
27+
.bench(ConfigSmall::store(37).bench())
28+
.bench(ConfigMedium::init(0).bench())
29+
.bench(ConfigMedium::init(1).bench())
30+
.bench(ConfigMedium::init(5).bench())
31+
.bench(ConfigMedium::init(10).bench())
32+
.bench(ConfigMedium::init(25).bench())
33+
.bench(ConfigMedium::init(37).bench())
34+
.bench(ConfigMedium::store(0).bench())
35+
.bench(ConfigMedium::store(1).bench())
36+
.bench(ConfigMedium::store(5).bench())
37+
.bench(ConfigMedium::store(10).bench())
38+
.bench(ConfigMedium::store(25).bench())
39+
.bench(ConfigMedium::store(37).bench())
40+
.bench(ConfigLarge::init(0).bench())
41+
.bench(ConfigLarge::init(1).bench())
42+
.bench(ConfigLarge::init(5).bench())
43+
.bench(ConfigLarge::init(10).bench())
44+
.bench(ConfigLarge::init(25).bench())
45+
.bench(ConfigLarge::init(37).bench())
46+
.bench(ConfigLarge::store(0).bench())
47+
.bench(ConfigLarge::store(1).bench())
48+
.bench(ConfigLarge::store(5).bench())
49+
.bench(ConfigLarge::store(10).bench())
50+
.bench(ConfigLarge::store(25).bench())
51+
.bench(ConfigLarge::store(37).bench())
52+
.must_pass(true)
53+
.out_dir("./benches")
54+
.execute();
55+
}

program/benches/setup.rs

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
use {
2+
mollusk_svm_bencher::Bench,
3+
serde::Serialize,
4+
solana_config_program::{
5+
instruction::store,
6+
state::{ConfigKeys, ConfigState},
7+
},
8+
solana_sdk::{
9+
account::AccountSharedData,
10+
hash::Hash,
11+
instruction::{AccountMeta, Instruction},
12+
pubkey::Pubkey,
13+
rent::Rent,
14+
},
15+
};
16+
17+
/// Helper struct to convert to a `Bench`.
18+
pub struct BenchContext {
19+
label: String,
20+
instruction: Instruction,
21+
accounts: Vec<(Pubkey, AccountSharedData)>,
22+
}
23+
24+
impl BenchContext {
25+
/// Convert to a `Bench`.
26+
pub fn bench(&self) -> Bench {
27+
(self.label.as_str(), &self.instruction, &self.accounts)
28+
}
29+
}
30+
31+
/// Trait to avoid re-defining the same instruction and account constructors
32+
/// for each `ConfigState`.
33+
pub trait BenchSetup: ConfigState + Default {
34+
const BENCH_ID: &'static str;
35+
36+
fn default_account_state(keys: Vec<(Pubkey, bool)>) -> (ConfigKeys, Self) {
37+
(ConfigKeys { keys }, Self::default())
38+
}
39+
40+
fn default_space(keys: Vec<(Pubkey, bool)>) -> usize {
41+
(Self::max_space()
42+
.checked_add(ConfigKeys::serialized_size(keys))
43+
.unwrap()) as usize
44+
}
45+
46+
fn keys(keys_len: usize) -> Vec<(Pubkey, bool)> {
47+
(0..keys_len)
48+
.map(|_| (Pubkey::new_unique(), false))
49+
.collect()
50+
}
51+
52+
fn init(keys_len: usize) -> BenchContext {
53+
let config_pubkey = Pubkey::new_unique();
54+
let keys = Self::keys(keys_len);
55+
let space = Self::default_space(keys.clone());
56+
let lamports = Rent::default().minimum_balance(space);
57+
58+
let instruction = {
59+
let account_metas = vec![AccountMeta::new(config_pubkey, true)];
60+
let account_data = Self::default_account_state(keys);
61+
Instruction::new_with_bincode(solana_config_program::id(), &account_data, account_metas)
62+
};
63+
64+
let accounts = vec![(
65+
config_pubkey,
66+
AccountSharedData::new(lamports, space, &solana_config_program::id()),
67+
)];
68+
69+
BenchContext {
70+
label: format!("{}_init_{}_keys", Self::BENCH_ID, keys_len),
71+
instruction,
72+
accounts,
73+
}
74+
}
75+
76+
fn store(keys_len: usize) -> BenchContext {
77+
let config_pubkey = Pubkey::new_unique();
78+
let keys = Self::keys(keys_len);
79+
let space = Self::default_space(keys.clone());
80+
let lamports = Rent::default().minimum_balance(space);
81+
82+
let instruction = store(&config_pubkey, true, keys.clone(), &Self::default());
83+
84+
let accounts = vec![(
85+
config_pubkey,
86+
AccountSharedData::new_data(
87+
lamports,
88+
&Self::default_account_state(keys),
89+
&solana_config_program::id(),
90+
)
91+
.unwrap(),
92+
)];
93+
94+
BenchContext {
95+
label: format!("{}_store_{}_keys", Self::BENCH_ID, keys_len),
96+
instruction,
97+
accounts,
98+
}
99+
}
100+
}
101+
102+
/// A small config, which just stores 8 bytes.
103+
#[derive(Debug, Default, PartialEq, Serialize)]
104+
pub struct ConfigSmall {
105+
pub item: u64,
106+
}
107+
108+
impl ConfigState for ConfigSmall {
109+
fn max_space() -> u64 {
110+
bincode::serialized_size(&Self::default()).unwrap()
111+
}
112+
}
113+
114+
impl BenchSetup for ConfigSmall {
115+
const BENCH_ID: &'static str = "config_small";
116+
}
117+
118+
/// A medium config, which stores 1024 bytes.
119+
#[derive(Debug, Default, PartialEq, Serialize)]
120+
pub struct ConfigMedium {
121+
pub hashes: [Hash; 32], // 32 x 32 = 1024 bytes
122+
}
123+
124+
impl ConfigState for ConfigMedium {
125+
fn max_space() -> u64 {
126+
bincode::serialized_size(&Self::default()).unwrap()
127+
}
128+
}
129+
130+
impl BenchSetup for ConfigMedium {
131+
const BENCH_ID: &'static str = "config_medium";
132+
}
133+
134+
/// A large config, which stores 32_768 bytes.
135+
#[derive(Debug, Default, PartialEq, Serialize)]
136+
pub struct ConfigLarge {
137+
pub hashes: [[Hash; 32]; 32], // 32 x 32 x 32 = 32_768 bytes
138+
}
139+
140+
impl ConfigState for ConfigLarge {
141+
fn max_space() -> u64 {
142+
bincode::serialized_size(&Self::default()).unwrap()
143+
}
144+
}
145+
146+
impl BenchSetup for ConfigLarge {
147+
const BENCH_ID: &'static str = "config_large";
148+
}

program/tests/functional.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,15 @@ fn setup() -> Mollusk {
4343

4444
fn get_config_space(key_len: usize) -> usize {
4545
let entry_size = bincode::serialized_size(&(Pubkey::default(), true)).unwrap() as usize;
46-
bincode::serialized_size(&(ConfigKeys::default(), MyConfig::default())).unwrap() as usize
47-
+ key_len * entry_size
46+
let total_keys_size = (key_len).checked_mul(entry_size).unwrap();
47+
let serialized_size =
48+
bincode::serialized_size(&(ConfigKeys::default(), MyConfig::default())).unwrap() as usize;
49+
serialized_size.checked_add(total_keys_size).unwrap()
4850
}
4951

5052
fn create_config_account(mollusk: &Mollusk, keys: Vec<(Pubkey, bool)>) -> AccountSharedData {
5153
let space = get_config_space(keys.len());
52-
let lamports = mollusk.sysvars.rent.minimum_balance(space as usize);
54+
let lamports = mollusk.sysvars.rent.minimum_balance(space);
5355
AccountSharedData::new_data(
5456
lamports,
5557
&(ConfigKeys { keys }, MyConfig::default()),
@@ -66,7 +68,7 @@ fn test_process_create_ok() {
6668
let config_account = {
6769
let space = get_config_space(0);
6870
let lamports = mollusk.sysvars.rent.minimum_balance(space);
69-
AccountSharedData::new(lamports, space as usize, &solana_config_program::id())
71+
AccountSharedData::new(lamports, space, &solana_config_program::id())
7072
};
7173

7274
// `instruction::initialize_account` without making it public...
@@ -518,7 +520,7 @@ fn test_config_bad_owner() {
518520
// Store a config account with the wrong owner.
519521
let config_account = {
520522
let space = get_config_space(keys.len());
521-
let lamports = mollusk.sysvars.rent.minimum_balance(space as usize);
523+
let lamports = mollusk.sysvars.rent.minimum_balance(space);
522524
AccountSharedData::new(lamports, 0, &Pubkey::new_unique())
523525
};
524526

0 commit comments

Comments
 (0)