Skip to content

Commit

Permalink
Merge pull request #2152 from CosmWasm/chipshort/gas-benchmarking
Browse files Browse the repository at this point in the history
Rebalance gas cost
  • Loading branch information
chipshort committed May 30, 2024
2 parents 9cd3350 + ebbe201 commit af4b239
Show file tree
Hide file tree
Showing 10 changed files with 120 additions and 63 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ and this project adheres to
- cosmwasm-std: Deprecate "compact" serialization of `Binary`, `HexBinary`,
`Checksum` ([#2125])
- cosmwasm-vm: Update wasmer to 4.3.1 ([#2147], [#2153])
- cosmwasm-vm: Rebalance gas costs for cryptographic functions and wasm
instructions. ([#2152])

[#2044]: https://github.com/CosmWasm/cosmwasm/pull/2044
[#2051]: https://github.com/CosmWasm/cosmwasm/pull/2051
Expand All @@ -88,6 +90,7 @@ and this project adheres to
[#2108]: https://github.com/CosmWasm/cosmwasm/pull/2108
[#2125]: https://github.com/CosmWasm/cosmwasm/pull/2125
[#2147]: https://github.com/CosmWasm/cosmwasm/pull/2147
[#2152]: https://github.com/CosmWasm/cosmwasm/pull/2152
[#2153]: https://github.com/CosmWasm/cosmwasm/pull/2153

## [2.0.1] - 2024-04-03
Expand Down
5 changes: 3 additions & 2 deletions contracts/crypto-verify/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@

use cosmwasm_std::{Binary, Response, Uint128};
use cosmwasm_vm::testing::{
instantiate, mock_env, mock_info, mock_instance, query, MockApi, MockQuerier, MockStorage,
instantiate, mock_env, mock_info, mock_instance_with_gas_limit, query, MockApi, MockQuerier,
MockStorage,
};
use cosmwasm_vm::{from_slice, Instance};
use hex_literal::hex;
Expand Down Expand Up @@ -96,7 +97,7 @@ fn build_drand_message(round: u64, previous_signature: &[u8]) -> Vec<u8> {
const DESERIALIZATION_LIMIT: usize = 20_000;

fn setup() -> Instance<MockApi, MockStorage, MockQuerier> {
let mut deps = mock_instance(WASM, &[]);
let mut deps = mock_instance_with_gas_limit(WASM, 10_000_000_000);
let msg = InstantiateMsg {};
let info = mock_info(CREATOR, &[]);
let res: Response = instantiate(&mut deps, mock_env(), info, msg).unwrap();
Expand Down
12 changes: 12 additions & 0 deletions docs/GAS.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,18 @@ gas and took 15ms on our CI system. The ideal cost per operation for this system
is `10**12 / (96837752 / (15 / 1000))`: 154. This is rounded to 150 for
simplicity.

CosmWasm 2.1 update: All gas values were re-evaluated and adjusted to meet the 1
Teragas/second target mentioned above. A rerun of the Argon2 test contract
consumed 5270718300 gas with the previous cost of 150, so the operation count
was `5270718300 / 150 = 35138122`. This took 6ms on our benchmark server, so the
new cost per operation is `10**12 / (35138122 / (6 / 1000))`: 171. This is
rounded to 170 for simplicity.

Benchmarking system:

- CPU: Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz (4 cores, 8 threads)
- RAM: 32GB DDR4 2133 MHz

Each machine is different, we know that. But the above target helps us in
multiple ways:

Expand Down
2 changes: 1 addition & 1 deletion packages/crypto/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ and [cosmwasm-std](`https://crates.io/crates/cosmwasm-std`) crates.

```
cd packages/crypto
cargo bench
cargo bench --features std
```

## License
Expand Down
13 changes: 7 additions & 6 deletions packages/crypto/benches/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,12 +157,13 @@ where
.map(|(secret_key, message)| *message * secret_key)
.collect();

for i in 1..=two_pow_max {
let num_points = 2_usize.pow(i);
let messages = &messages[..num_points];
let keys = &public_keys[..num_points];
for i in 0..=two_pow_max {
let n = 2_usize.pow(i); // the number of pairings on the left hand side
let k = n + 1; // the number of pairings in total
let messages = &messages[..n];
let keys = &public_keys[..n];
let aggregated_signature: G2Affine =
signatures[..num_points].iter().sum::<G2Projective>().into();
signatures[..n].iter().sum::<G2Projective>().into();

let serialized_pubkeys: Vec<u8> = keys
.iter()
Expand All @@ -187,7 +188,7 @@ where
.serialize_compressed(&mut serialized_signature[..])
.unwrap();

group.bench_function(format!("bls12_381_pairing_equality_{num_points}"), |b| {
group.bench_function(format!("bls12_381_pairing_equality_k={k}"), |b| {
b.iter(|| {
let is_valid = black_box(bls12_381_pairing_equality(
&serialized_pubkeys,
Expand Down
91 changes: 60 additions & 31 deletions packages/vm/src/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,55 +42,84 @@ pub struct GasConfig {
/// ed25519 signature verification cost
pub ed25519_verify_cost: u64,
/// ed25519 batch signature verification cost
pub ed25519_batch_verify_cost: u64,
pub ed25519_batch_verify_cost: LinearGasCost,
/// ed25519 batch signature verification cost (single public key)
pub ed25519_batch_verify_one_pubkey_cost: u64,
/// bls12-381 aggregate cost per point (g1)
pub bls12_381_aggregate_g1_per_point: u64,
/// bls12-381 aggregate cost per point (g2)
pub bls12_381_aggregate_g2_per_point: u64,
pub ed25519_batch_verify_one_pubkey_cost: LinearGasCost,
/// bls12-381 aggregate cost (g1)
pub bls12_381_aggregate_g1_cost: LinearGasCost,
/// bls12-381 aggregate cost (g2)
pub bls12_381_aggregate_g2_cost: LinearGasCost,
/// bls12-381 hash to g1 cost
pub bls12_381_hash_to_g1_cost: u64,
/// bls12-381 hash to g2 cost
pub bls12_381_hash_to_g2_cost: u64,
/// bls12-381 pairing equality check cost
pub bls12_381_pairing_equality_cost: u64,
/// bls12-381 aggregated pairing equality check cost per point
/// (added on top of the base pairing equality check cost)
pub bls12_381_aggregated_pairing_equality_cost_per_pair: u64,
pub bls12_381_pairing_equality_cost: LinearGasCost,
}

impl Default for GasConfig {
fn default() -> Self {
// Target is 10^12 per second (see GAS.md), i.e. 10^6 gas per µ second.
const GAS_PER_US: u64 = 1_000_000;
Self {
// ~119 us in crypto benchmarks
secp256k1_verify_cost: 119 * GAS_PER_US,
// ~233 us in crypto benchmarks
secp256k1_recover_pubkey_cost: 233 * GAS_PER_US,
// ~374 us in crypto benchmarks
secp256r1_verify_cost: 374 * GAS_PER_US,
// ~834 us in crypto benchmarks
secp256r1_recover_pubkey_cost: 834 * GAS_PER_US,
// ~63 us in crypto benchmarks
ed25519_verify_cost: 63 * GAS_PER_US,
// Gas cost factors, relative to ed25519_verify cost
// From https://docs.rs/ed25519-zebra/2.2.0/ed25519_zebra/batch/index.html
ed25519_batch_verify_cost: 63 * GAS_PER_US / 2,
ed25519_batch_verify_one_pubkey_cost: 63 * GAS_PER_US / 4,
// ~96 us in crypto benchmarks
secp256k1_verify_cost: 96 * GAS_PER_US,
// ~194 us in crypto benchmarks
secp256k1_recover_pubkey_cost: 194 * GAS_PER_US,
// ~279 us in crypto benchmarks
secp256r1_verify_cost: 279 * GAS_PER_US,
// ~592 us in crypto benchmarks
secp256r1_recover_pubkey_cost: 592 * GAS_PER_US,
// ~35 us in crypto benchmarks
ed25519_verify_cost: 35 * GAS_PER_US,
// Calculated based on the benchmark results for `ed25519_batch_verify_{x}`.
ed25519_batch_verify_cost: LinearGasCost {
base: 24 * GAS_PER_US,
per_item: 21 * GAS_PER_US,
},
// Calculated based on the benchmark results for `ed25519_batch_verify_one_pubkey_{x}`.
ed25519_batch_verify_one_pubkey_cost: LinearGasCost {
base: 36 * GAS_PER_US,
per_item: 10 * GAS_PER_US,
},
// just assume the production machines have more than 4 cores, so we can half that
bls12_381_aggregate_g1_per_point: 16 * GAS_PER_US / 2,
bls12_381_aggregate_g2_per_point: 33 * GAS_PER_US / 2,
bls12_381_hash_to_g1_cost: 324 * GAS_PER_US,
bls12_381_hash_to_g2_cost: 528 * GAS_PER_US,
// god i wish i was lying
bls12_381_pairing_equality_cost: 1038 * GAS_PER_US,
bls12_381_aggregated_pairing_equality_cost_per_pair: 108 * GAS_PER_US,
bls12_381_aggregate_g1_cost: LinearGasCost {
base: 136 * GAS_PER_US / 2,
per_item: 24 * GAS_PER_US / 2,
},
bls12_381_aggregate_g2_cost: LinearGasCost {
base: 207 * GAS_PER_US / 2,
per_item: 49 * GAS_PER_US / 2,
},
bls12_381_hash_to_g1_cost: 563 * GAS_PER_US,
bls12_381_hash_to_g2_cost: 871 * GAS_PER_US,
bls12_381_pairing_equality_cost: LinearGasCost {
base: 2112 * GAS_PER_US,
per_item: 163 * GAS_PER_US,
},
}
}
}

/// Linear gas cost model where the cost is linear in the number of items.
///
/// To calculate it, you sample the cost for a few different amounts of items and fit a line to it.
/// Let `b` be that line of best fit. Then `base = b(0)` is the y-intercept and
/// `per_item = b(1) - b(0)` the slope.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct LinearGasCost {
/// This is a flat part of the cost, charged once per batch.
base: u64,
/// This is the cost per item in the batch.
per_item: u64,
}

impl LinearGasCost {
pub fn total_cost(&self, items: u64) -> u64 {
self.base + self.per_item * items
}
}

/** context data **/

#[derive(Clone, PartialEq, Eq, Debug, Default)]
Expand Down
38 changes: 22 additions & 16 deletions packages/vm/src/imports.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
//! Import implementations

use std::cmp::max;
use std::marker::PhantomData;

use cosmwasm_crypto::{
Expand Down Expand Up @@ -253,7 +252,7 @@ const BLS12_381_AGGREGATE_SUCCESS: u32 = 0;
/// Return code (error code) for success when hashing to the curve
const BLS12_381_HASH_TO_CURVE_SUCCESS: u32 = 0;

/// Maximum size of continous points passed to aggregate functions
/// Maximum size of continuous points passed to aggregate functions
const BLS12_381_MAX_AGGREGATE_SIZE: usize = 2 * MI;

/// Maximum size of the message passed to the hash-to-curve functions
Expand All @@ -278,7 +277,9 @@ pub fn do_bls12_381_aggregate_g1<

let estimated_point_count = (g1s.len() / BLS12_381_G1_POINT_LEN) as u64;
let gas_info = GasInfo::with_cost(
data.gas_config.bls12_381_aggregate_g1_per_point * estimated_point_count,
data.gas_config
.bls12_381_aggregate_g1_cost
.total_cost(estimated_point_count),
);
process_gas_info(data, &mut store, gas_info)?;

Expand Down Expand Up @@ -322,7 +323,9 @@ pub fn do_bls12_381_aggregate_g2<

let estimated_point_count = (g2s.len() / BLS12_381_G2_POINT_LEN) as u64;
let gas_info = GasInfo::with_cost(
data.gas_config.bls12_381_aggregate_g2_per_point * estimated_point_count,
data.gas_config
.bls12_381_aggregate_g2_cost
.total_cost(estimated_point_count),
);
process_gas_info(data, &mut store, gas_info)?;

Expand Down Expand Up @@ -369,15 +372,18 @@ pub fn do_bls12_381_pairing_equality<
let r = read_region(&memory, r_ptr, BLS12_381_G1_POINT_LEN)?;
let s = read_region(&memory, s_ptr, BLS12_381_G2_POINT_LEN)?;

let estimated_point_count = (ps.len() / BLS12_381_G1_POINT_LEN) as u64;
let additional_cost = data
.gas_config
.bls12_381_aggregated_pairing_equality_cost_per_pair
// Add one since we do not include any pairs in the base benchmark, and we always need to add one for the `r` and `s` pair.
* (estimated_point_count + 1);
// The values here are only correct if ps and qs can be divided by the point size.
// They are good enough for gas since we error in `bls12_381_pairing_equality` if the inputs are
// not properly formatted.
let estimated_n = (ps.len() / BLS12_381_G1_POINT_LEN) as u64;
// The number of parings to compute (`n` on the left hand side and `k = n + 1` in total)
let estimated_k = estimated_n + 1;

let gas_info =
GasInfo::with_cost(data.gas_config.bls12_381_pairing_equality_cost + additional_cost);
let gas_info = GasInfo::with_cost(
data.gas_config
.bls12_381_pairing_equality_cost
.total_cost(estimated_k),
);
process_gas_info(data, &mut store, gas_info)?;

let code = match bls12_381_pairing_equality(&ps, &qs, &r, &s) {
Expand Down Expand Up @@ -729,11 +735,11 @@ pub fn do_ed25519_batch_verify<
let public_keys = decode_sections(&public_keys);

let gas_cost = if public_keys.len() == 1 {
data.gas_config.ed25519_batch_verify_one_pubkey_cost
&data.gas_config.ed25519_batch_verify_one_pubkey_cost
} else {
data.gas_config.ed25519_batch_verify_cost
} * signatures.len() as u64;
let gas_info = GasInfo::with_cost(max(gas_cost, data.gas_config.ed25519_verify_cost));
&data.gas_config.ed25519_batch_verify_cost
};
let gas_info = GasInfo::with_cost(gas_cost.total_cost(signatures.len() as u64));
process_gas_info(data, &mut store, gas_info)?;
let result = ed25519_batch_verify(&mut OsRng, &messages, &signatures, &public_keys);
let code = match result {
Expand Down
8 changes: 4 additions & 4 deletions packages/vm/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -914,7 +914,7 @@ mod tests {

let report2 = instance.create_gas_report();
assert_eq!(report2.used_externally, 251);
assert_eq!(report2.used_internally, 8461548);
assert_eq!(report2.used_internally, 12109530);
assert_eq!(report2.limit, LIMIT);
assert_eq!(
report2.remaining,
Expand Down Expand Up @@ -1105,7 +1105,7 @@ mod tests {
.unwrap();

let init_used = orig_gas - instance.get_gas_left();
assert_eq!(init_used, 8461799);
assert_eq!(init_used, 12109781);
}

#[test]
Expand All @@ -1130,7 +1130,7 @@ mod tests {
.unwrap();

let execute_used = gas_before_execute - instance.get_gas_left();
assert_eq!(execute_used, 11181706);
assert_eq!(execute_used, 12658786);
}

#[test]
Expand Down Expand Up @@ -1173,6 +1173,6 @@ mod tests {
);

let query_used = gas_before_query - instance.get_gas_left();
assert_eq!(query_used, 7142556);
assert_eq!(query_used, 8094896);
}
}
9 changes: 7 additions & 2 deletions packages/vm/src/testing/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@ use crate::backend::unwrap_or_return_with_gas;
use crate::{Backend, BackendApi, BackendError, BackendResult, GasInfo};

pub const MOCK_CONTRACT_ADDR: &str = "cosmwasmcontract"; // TODO: use correct address
const GAS_COST_HUMANIZE: u64 = 44; // TODO: these seem very low
const GAS_COST_CANONICALIZE: u64 = 55;
/// Default gas multiplier in wasmd.
/// See https://github.com/CosmWasm/wasmd/blob/v0.51.0/x/wasm/types/gas_register.go#L34
const WASMD_GAS_MULTIPLIER: u64 = 140_000;
/// See https://github.com/CosmWasm/wasmd/blob/v0.51.0/x/wasm/keeper/api.go#L27
const GAS_COST_HUMANIZE: u64 = 4 * WASMD_GAS_MULTIPLIER;
/// See https://github.com/CosmWasm/wasmd/blob/v0.51.0/x/wasm/keeper/api.go#L28
const GAS_COST_CANONICALIZE: u64 = 5 * WASMD_GAS_MULTIPLIER;

/// Default prefix used when creating Bech32 encoded address.
const BECH32_PREFIX: &str = "cosmwasm";
Expand Down
2 changes: 1 addition & 1 deletion packages/vm/src/wasm_backend/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ fn cost(_operator: &Operator) -> u64 {
// In https://github.com/CosmWasm/cosmwasm/pull/1042 a profiler is developed to
// identify runtime differences between different Wasm operation, but this is not yet
// precise enough to derive insights from it.
150
170
}

/// Use Cranelift as the compiler backend if the feature is enabled
Expand Down

0 comments on commit af4b239

Please sign in to comment.