Some benchmarks for phragmen (#2650)

* Add some benchmarks for phragmen

* Fix dep. import.

* Clean up with some macros.

* more details.

* Fix dual import.

* Remove wrong assertions.

* Add a few more.
This commit is contained in:
Kian Peymani
2019-06-05 11:42:29 +02:00
committed by Gavin Wood
parent 012ce5878b
commit a5964e4055
7 changed files with 193 additions and 74 deletions
+1
View File
@@ -3692,6 +3692,7 @@ name = "srml-staking"
version = "2.0.0"
dependencies = [
"parity-codec 3.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"safe-mix 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)",
"sr-io 2.0.0",
+2
View File
@@ -21,8 +21,10 @@ session = { package = "srml-session", path = "../session", default-features = fa
substrate-primitives = { path = "../../core/primitives" }
timestamp = { package = "srml-timestamp", path = "../timestamp" }
balances = { package = "srml-balances", path = "../balances" }
rand = "0.6.5"
[features]
bench = []
default = ["std"]
std = [
"serde",
+114
View File
@@ -0,0 +1,114 @@
// Copyright 2019 Parity Technologies
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Benchmarks of the phragmen election algorithm.
//! Note that execution times will not be accurate in an absolute scale, since
//! - Everything is executed in the context of `TestExternalities`
//! - Everything is executed in native environment.
//!
//! Run using:
//!
//! ```zsh
//! cargo bench --features bench --color always
//! ```
use test::Bencher;
use runtime_io::with_externalities;
use mock::*;
use super::*;
use rand::{self, Rng};
const VALIDATORS: u64 = 1000;
const NOMINATORS: u64 = 10_000;
const EDGES: u64 = 2;
const TO_ELECT: usize = 100;
const STAKE: u64 = 1000;
fn do_phragmen(b: &mut Bencher, num_vals: u64, num_noms: u64, count: usize, votes_per: u64) {
with_externalities(&mut ExtBuilder::default().nominate(false).build(), || {
assert!(num_vals > votes_per);
let rr = |a, b| rand::thread_rng().gen_range(a as usize, b as usize) as u64;
// prefix to distinguish the validator and nominator account ranges.
let np = 10_000;
(1 ..= 2*num_vals)
.step_by(2)
.for_each(|acc| bond_validator(acc, STAKE + rr(10, 50)));
(np ..= (np + 2*num_noms))
.step_by(2)
.for_each(|acc| {
let mut stashes_to_vote = (1 ..= 2*num_vals)
.step_by(2)
.map(|ctrl| ctrl + 1)
.collect::<Vec<AccountIdType>>();
let votes = (0 .. votes_per)
.map(|_| {
stashes_to_vote.remove(rr(0, stashes_to_vote.len()) as usize)
})
.collect::<Vec<AccountIdType>>();
bond_nominator(acc, STAKE + rr(10, 50), votes);
});
b.iter(|| {
let _ = phragmen::elect::<Test, _, _, _>(
count,
1_usize,
<Validators<Test>>::enumerate(),
<Nominators<Test>>::enumerate(),
Staking::slashable_balance_of
);
})
})
}
macro_rules! phragmen_benches {
($($name:ident: $tup:expr,)*) => {
$(
#[bench]
fn $name(b: &mut Bencher) {
let (v, n, t, e) = $tup;
println!("");
println!(
"++ Benchmark: {} Validators // {} Nominators // {} Edges-per-nominator // {} total edges // electing {}",
v, n, e, e * n, t
);
do_phragmen(b, v, n, t, e);
}
)*
}
}
phragmen_benches! {
bench_1_1: (VALIDATORS, NOMINATORS, TO_ELECT, EDGES),
bench_1_2: (VALIDATORS*2, NOMINATORS, TO_ELECT, EDGES),
bench_1_3: (VALIDATORS*4, NOMINATORS, TO_ELECT, EDGES),
bench_1_4: (VALIDATORS*8, NOMINATORS, TO_ELECT, EDGES),
bench_0_1: (VALIDATORS, NOMINATORS, TO_ELECT, EDGES),
bench_0_2: (VALIDATORS, NOMINATORS, TO_ELECT * 4, EDGES),
bench_0_3: (VALIDATORS, NOMINATORS, TO_ELECT * 8, EDGES),
bench_0_4: (VALIDATORS, NOMINATORS, TO_ELECT * 16, EDGES),
bench_2_1: (VALIDATORS, NOMINATORS, TO_ELECT, EDGES),
bench_2_2: (VALIDATORS, NOMINATORS*2, TO_ELECT, EDGES),
bench_2_3: (VALIDATORS, NOMINATORS*4, TO_ELECT, EDGES),
bench_2_4: (VALIDATORS, NOMINATORS*8, TO_ELECT, EDGES),
bench_3_1: (VALIDATORS, NOMINATORS, TO_ELECT, EDGES),
bench_3_2: (VALIDATORS, NOMINATORS, TO_ELECT, EDGES*2),
bench_3_3: (VALIDATORS, NOMINATORS, TO_ELECT, EDGES*4),
bench_3_4: (VALIDATORS, NOMINATORS, TO_ELECT, EDGES*8),
}
+20 -9
View File
@@ -240,16 +240,31 @@
//! stored in the Session module's `Validators` at the end of each era.
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(all(feature = "bench", test), feature(test))]
#[cfg(all(feature = "bench", test))]
extern crate test;
#[cfg(any(feature = "bench", test))]
mod mock;
#[cfg(test)]
mod tests;
mod phragmen;
#[cfg(all(feature = "bench", test))]
mod benches;
#[cfg(feature = "std")]
use runtime_io::with_storage;
use rstd::{prelude::*, result, collections::btree_map::BTreeMap};
use parity_codec::{HasCompact, Encode, Decode};
use srml_support::{StorageValue, StorageMap, EnumerableStorageMap, dispatch::Result};
use srml_support::{decl_module, decl_event, decl_storage, ensure};
use srml_support::traits::{
Currency, OnFreeBalanceZero, OnDilution, LockIdentifier, LockableCurrency, WithdrawReasons,
OnUnbalanced, Imbalance,
use srml_support::{ StorageValue, StorageMap, EnumerableStorageMap, dispatch::Result,
decl_module, decl_event, decl_storage, ensure,
traits::{Currency, OnFreeBalanceZero, OnDilution, LockIdentifier, LockableCurrency,
WithdrawReasons, OnUnbalanced, Imbalance
}
};
use session::OnSessionChange;
use primitives::Perbill;
@@ -261,10 +276,6 @@ use primitives::traits::{
use primitives::{Serialize, Deserialize};
use system::ensure_signed;
mod mock;
mod tests;
mod phragmen;
use phragmen::{elect, ACCURACY, ExtendedBalance};
const RECENT_OFFLINE_COUNT: usize = 32;
+32 -4
View File
@@ -16,14 +16,12 @@
//! Test utilities
#![cfg(test)]
use primitives::{traits::{IdentityLookup, Convert}, BuildStorage, Perbill};
use primitives::testing::{Digest, DigestItem, Header, UintAuthorityId, ConvertUintAuthorityId};
use substrate_primitives::{H256, Blake2Hasher};
use runtime_io;
use srml_support::impl_outer_origin;
use crate::{GenesisConfig, Module, Trait, StakerStatus};
use srml_support::{impl_outer_origin, assert_ok, traits::Currency};
use crate::{GenesisConfig, Module, Trait, StakerStatus, ValidatorPrefs, RewardDestination};
/// The AccountId alias in this test module.
pub type AccountIdType = u64;
@@ -241,3 +239,33 @@ pub type Balances = balances::Module<Test>;
pub type Session = session::Module<Test>;
pub type Timestamp = timestamp::Module<Test>;
pub type Staking = Module<Test>;
pub fn check_exposure(acc: u64) {
let expo = Staking::stakers(&acc);
assert_eq!(expo.total as u128, expo.own as u128 + expo.others.iter().map(|e| e.value as u128).sum::<u128>());
}
pub fn check_exposure_all() {
Staking::current_elected().into_iter().for_each(|acc| check_exposure(acc));
}
pub fn assert_total_expo(acc: u64, val: u64) {
let expo = Staking::stakers(&acc);
assert_eq!(expo.total, val);
}
pub fn bond_validator(acc: u64, val: u64) {
// a = controller
// a + 1 = stash
let _ = Balances::make_free_balance_be(&(acc+1), val);
assert_ok!(Staking::bond(Origin::signed(acc+1), acc, val, RewardDestination::Controller));
assert_ok!(Staking::validate(Origin::signed(acc), ValidatorPrefs::default()));
}
pub fn bond_nominator(acc: u64, val: u64, target: Vec<u64>) {
// a = controller
// a + 1 = stash
let _ = Balances::make_free_balance_be(&(acc+1), val);
assert_ok!(Staking::bond(Origin::signed(acc+1), acc, val, RewardDestination::Controller));
assert_ok!(Staking::nominate(Origin::signed(acc), target));
}
+23 -23
View File
@@ -107,29 +107,29 @@ pub fn elect<T: Trait + 'static, FV, FN, FS>(
// Candidates who have 0 stake => have no votes or all null-votes. Kick them out not.
let mut nominators: Vec<Nominator<T::AccountId>> = Vec::with_capacity(validator_iter.size_hint().0 + nominator_iter.size_hint().0);
let mut candidates = validator_iter.map(|(who, _)| {
let stash_balance = stash_of(&who);
(Candidate { who, ..Default::default() }, stash_balance)
})
.filter_map(|(mut c, s)| {
c.approval_stake += to_votes(s);
if c.approval_stake.is_zero() {
None
} else {
Some((c, s))
}
})
.enumerate()
.map(|(idx, (c, s))| {
nominators.push(Nominator {
who: c.who.clone(),
edges: vec![ Edge { who: c.who.clone(), candidate_index: idx, ..Default::default() }],
budget: to_votes(s),
load: Fraction::zero(),
});
c_idx_cache.insert(c.who.clone(), idx);
c
})
.collect::<Vec<Candidate<T::AccountId>>>();
let stash_balance = stash_of(&who);
(Candidate { who, ..Default::default() }, stash_balance)
})
.filter_map(|(mut c, s)| {
c.approval_stake += to_votes(s);
if c.approval_stake.is_zero() {
None
} else {
Some((c, s))
}
})
.enumerate()
.map(|(idx, (c, s))| {
nominators.push(Nominator {
who: c.who.clone(),
edges: vec![ Edge { who: c.who.clone(), candidate_index: idx, ..Default::default() }],
budget: to_votes(s),
load: Fraction::zero(),
});
c_idx_cache.insert(c.who.clone(), idx);
c
})
.collect::<Vec<Candidate<T::AccountId>>>();
// 2- Collect the nominators with the associated votes.
// Also collect approval stake along the way.
+1 -38
View File
@@ -16,50 +16,13 @@
//! Tests for the module.
#![cfg(test)]
use super::*;
use runtime_io::with_externalities;
use phragmen;
use srml_support::{assert_ok, assert_noop, assert_eq_uvec, EnumerableStorageMap};
use mock::{Balances, Session, Staking, System, Timestamp, Test, ExtBuilder, Origin};
use mock::*;
use srml_support::traits::{Currency, ReservableCurrency};
#[inline]
fn check_exposure(acc: u64) {
let expo = Staking::stakers(&acc);
assert_eq!(expo.total as u128, expo.own as u128 + expo.others.iter().map(|e| e.value as u128).sum::<u128>());
}
#[inline]
fn check_exposure_all() {
Staking::current_elected().into_iter().for_each(|acc| check_exposure(acc));
}
#[inline]
fn assert_total_expo(acc: u64, val: u64) {
let expo = Staking::stakers(&acc);
assert_eq!(expo.total, val);
}
#[inline]
fn bond_validator(acc: u64, val: u64) {
// a = controller
// a + 1 = stash
let _ = Balances::make_free_balance_be(&(acc+1), val);
assert_ok!(Staking::bond(Origin::signed(acc+1), acc, val, RewardDestination::Controller));
assert_ok!(Staking::validate(Origin::signed(acc), ValidatorPrefs::default()));
}
#[inline]
fn bond_nominator(acc: u64, val: u64, target: Vec<u64>) {
// a = controller
// a + 1 = stash
let _ = Balances::make_free_balance_be(&(acc+1), val);
assert_ok!(Staking::bond(Origin::signed(acc+1), acc, val, RewardDestination::Controller));
assert_ok!(Staking::nominate(Origin::signed(acc), target));
}
#[test]
fn basic_setup_works() {
// Verifies initial conditions of mock