Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.

Commit deac632

Browse files
kianenigmaParity Bot
and
Parity Bot
authored
Make election benchmarks more *memory-aware* (#9286)
* Make benchmarks a bit better with mem * Make election benchmarks more *memory-aware* * Fix a few errors * cargo run --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_election_provider_multi_phase --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/election-provider-multi-phase/src/weights.rs --template=./.maintain/frame-weight-template.hbs * Manually fix the weights * Update lock file * remove dupe * Fix tests * cargo update pwasm Co-authored-by: Parity Bot <[email protected]>
1 parent 279369e commit deac632

File tree

11 files changed

+356
-148
lines changed

11 files changed

+356
-148
lines changed

bin/node/runtime/src/lib.rs

+14-2
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,19 @@ sp_npos_elections::generate_solution_type!(
554554
pub const MAX_NOMINATIONS: u32 =
555555
<NposCompactSolution16 as sp_npos_elections::CompactSolution>::LIMIT as u32;
556556

557+
/// The numbers configured here should always be more than the the maximum limits of staking pallet
558+
/// to ensure election snapshot will not run out of memory.
559+
pub struct BenchmarkConfig;
560+
impl pallet_election_provider_multi_phase::BenchmarkingConfig for BenchmarkConfig {
561+
const VOTERS: [u32; 2] = [5_000, 10_000];
562+
const TARGETS: [u32; 2] = [1_000, 2_000];
563+
const ACTIVE_VOTERS: [u32; 2] = [1000, 4_000];
564+
const DESIRED_TARGETS: [u32; 2] = [400, 800];
565+
const SNAPSHOT_MAXIMUM_VOTERS: u32 = 25_000;
566+
const MINER_MAXIMUM_VOTERS: u32 = 15_000;
567+
const MAXIMUM_TARGETS: u32 = 2000;
568+
}
569+
557570
impl pallet_election_provider_multi_phase::Config for Runtime {
558571
type Event = Event;
559572
type Currency = Balances;
@@ -579,7 +592,7 @@ impl pallet_election_provider_multi_phase::Config for Runtime {
579592
type Fallback = Fallback;
580593
type WeightInfo = pallet_election_provider_multi_phase::weights::SubstrateWeight<Runtime>;
581594
type ForceOrigin = EnsureRootOrHalfCouncil;
582-
type BenchmarkingConfig = ();
595+
type BenchmarkingConfig = BenchmarkConfig;
583596
}
584597

585598
parameter_types! {
@@ -1578,7 +1591,6 @@ impl_runtime_apis! {
15781591
add_benchmark!(params, batches, pallet_uniques, Uniques);
15791592
add_benchmark!(params, batches, pallet_utility, Utility);
15801593
add_benchmark!(params, batches, pallet_vesting, Vesting);
1581-
add_benchmark!(params, batches, pallet_election_provider_multi_phase, ElectionProviderMultiPhase);
15821594

15831595
if batches.is_empty() { return Err("Benchmark not found for this pallet.".into()) }
15841596
Ok((batches, storage_info))

client/allocator/src/freeing_bump.rs

+10-9
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@
3838
//! allocation size is capped, therefore the number of orders and thus the linked lists is as well
3939
//! limited. Currently, the maximum size of an allocation is 32 MiB.
4040
//!
41-
//! When the allocator serves an allocation request it first checks the linked list for the respective
42-
//! order. If it doesn't have any free chunks, the allocator requests memory from the bump allocator.
43-
//! In any case the order is stored in the header of the allocation.
41+
//! When the allocator serves an allocation request it first checks the linked list for the
42+
//! respective order. If it doesn't have any free chunks, the allocator requests memory from the
43+
//! bump allocator. In any case the order is stored in the header of the allocation.
4444
//!
4545
//! Upon deallocation we get the order of the allocation from its header and then add that
4646
//! allocation to the linked list for the respective order.
@@ -59,12 +59,13 @@
5959
//! allocator was consumed by the 32 MiB allocation, allocations of all sizes except 32 MiB will
6060
//! fail.
6161
//!
62-
//! - Sizes of allocations are rounded up to the nearest order. That is, an allocation of 2,00001 MiB
63-
//! will be put into the bucket of 4 MiB. Therefore, any allocation of size `(N, 2N]` will take
64-
//! up to `2N`, thus assuming a uniform distribution of allocation sizes, the average amount in use
65-
//! of a `2N` space on the heap will be `(3N + ε) / 2`. So average utilisation is going to be around
66-
//! 75% (`(3N + ε) / 2 / 2N`) meaning that around 25% of the space in allocation will be wasted.
67-
//! This is more pronounced (in terms of absolute heap amounts) with larger allocation sizes.
62+
//! - Sizes of allocations are rounded up to the nearest order. That is, an allocation of 2,00001
63+
//! MiB will be put into the bucket of 4 MiB. Therefore, any allocation of size `(N, 2N]` will
64+
//! take up to `2N`, thus assuming a uniform distribution of allocation sizes, the average amount
65+
//! in use of a `2N` space on the heap will be `(3N + ε) / 2`. So average utilization is going to
66+
//! be around 75% (`(3N + ε) / 2 / 2N`) meaning that around 25% of the space in allocation will be
67+
//! wasted. This is more pronounced (in terms of absolute heap amounts) with larger allocation
68+
//! sizes.
6869
6970
use crate::Error;
7071
use std::{mem, convert::{TryFrom, TryInto}, ops::{Range, Index, IndexMut}};

frame/election-provider-multi-phase/src/benchmarking.rs

+123-56
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,9 @@
2020
use super::*;
2121
use crate::{Pallet as MultiPhase, unsigned::IndexAssignmentOf};
2222
use frame_benchmarking::{account, impl_benchmark_test_suite};
23-
use frame_support::{assert_ok, traits::OnInitialize};
23+
use frame_support::{assert_ok, traits::Hooks};
2424
use frame_system::RawOrigin;
2525
use rand::{prelude::SliceRandom, rngs::SmallRng, SeedableRng};
26-
use frame_election_provider_support::Assignment;
2726
use sp_arithmetic::{per_things::Percent, traits::One};
2827
use sp_npos_elections::IndexAssignment;
2928
use sp_runtime::InnerOf;
@@ -38,14 +37,14 @@ fn solution_with_size<T: Config>(
3837
size: SolutionOrSnapshotSize,
3938
active_voters_count: u32,
4039
desired_targets: u32,
41-
) -> RawSolution<CompactOf<T>> {
42-
assert!(size.targets >= desired_targets, "must have enough targets");
43-
assert!(
40+
) -> Result<RawSolution<CompactOf<T>>, &'static str> {
41+
ensure!(size.targets >= desired_targets, "must have enough targets");
42+
ensure!(
4443
size.targets >= (<CompactOf<T>>::LIMIT * 2) as u32,
4544
"must have enough targets for unique votes."
4645
);
47-
assert!(size.voters >= active_voters_count, "must have enough voters");
48-
assert!(
46+
ensure!(size.voters >= active_voters_count, "must have enough voters");
47+
ensure!(
4948
(<CompactOf<T>>::LIMIT as u32) < desired_targets,
5049
"must have enough winners to give them votes."
5150
);
@@ -125,7 +124,7 @@ fn solution_with_size<T: Config>(
125124
.map(|(voter, _stake, votes)| {
126125
let percent_per_edge: InnerOf<CompactAccuracyOf<T>> =
127126
(100 / votes.len()).try_into().unwrap_or_else(|_| panic!("failed to convert"));
128-
Assignment {
127+
crate::unsigned::Assignment::<T> {
129128
who: voter.clone(),
130129
distribution: votes
131130
.iter()
@@ -141,7 +140,31 @@ fn solution_with_size<T: Config>(
141140
let round = <MultiPhase<T>>::round();
142141

143142
assert!(score[0] > 0, "score is zero, this probably means that the stakes are not set.");
144-
RawSolution { compact, score, round }
143+
Ok(RawSolution { compact, score, round })
144+
}
145+
146+
fn set_up_data_provider<T: Config>(v: u32, t: u32) {
147+
// number of votes in snapshot.
148+
149+
T::DataProvider::clear();
150+
log!(info, "setting up with voters = {} [degree = {}], targets = {}", v, T::DataProvider::MAXIMUM_VOTES_PER_VOTER, t);
151+
152+
// fill targets.
153+
let mut targets = (0..t).map(|i| {
154+
let target = frame_benchmarking::account::<T::AccountId>("Target", i, SEED);
155+
T::DataProvider::add_target(target.clone());
156+
target
157+
}).collect::<Vec<_>>();
158+
// we should always have enough voters to fill.
159+
assert!(targets.len() > T::DataProvider::MAXIMUM_VOTES_PER_VOTER as usize);
160+
targets.truncate(T::DataProvider::MAXIMUM_VOTES_PER_VOTER as usize);
161+
162+
// fill voters.
163+
(0..v).for_each(|i| {
164+
let voter = frame_benchmarking::account::<T::AccountId>("Voter", i, SEED);
165+
let weight = T::Currency::minimum_balance().saturated_into::<u64>() * 1000;
166+
T::DataProvider::add_voter(voter, weight, targets.clone());
167+
});
145168
}
146169

147170
frame_benchmarking::benchmarks! {
@@ -223,14 +246,18 @@ frame_benchmarking::benchmarks! {
223246

224247
// a call to `<Pallet as ElectionProvider>::elect` where we only return the queued solution.
225248
elect_queued {
226-
// assume largest values for the election status. These will merely affect the decoding.
227-
let v = T::BenchmarkingConfig::VOTERS[1];
228-
let t = T::BenchmarkingConfig::TARGETS[1];
229-
let a = T::BenchmarkingConfig::ACTIVE_VOTERS[1];
230-
let d = T::BenchmarkingConfig::DESIRED_TARGETS[1];
249+
// number of votes in snapshot.
250+
let v in (T::BenchmarkingConfig::VOTERS[0]) .. T::BenchmarkingConfig::VOTERS[1];
251+
// number of targets in snapshot.
252+
let t in (T::BenchmarkingConfig::TARGETS[0]) .. T::BenchmarkingConfig::TARGETS[1];
253+
// number of assignments, i.e. compact.len(). This means the active nominators, thus must be
254+
// a subset of `v` component.
255+
let a in (T::BenchmarkingConfig::ACTIVE_VOTERS[0]) .. T::BenchmarkingConfig::ACTIVE_VOTERS[1];
256+
// number of desired targets. Must be a subset of `t` component.
257+
let d in (T::BenchmarkingConfig::DESIRED_TARGETS[0]) .. T::BenchmarkingConfig::DESIRED_TARGETS[1];
231258

232259
let witness = SolutionOrSnapshotSize { voters: v, targets: t };
233-
let raw_solution = solution_with_size::<T>(witness, a, d);
260+
let raw_solution = solution_with_size::<T>(witness, a, d)?;
234261
let ready_solution =
235262
<MultiPhase<T>>::feasibility_check(raw_solution, ElectionCompute::Signed).unwrap();
236263

@@ -251,15 +278,6 @@ frame_benchmarking::benchmarks! {
251278
assert_eq!(<CurrentPhase<T>>::get(), <Phase<T::BlockNumber>>::Off);
252279
}
253280

254-
#[extra]
255-
create_snapshot {
256-
assert!(<MultiPhase<T>>::snapshot().is_none());
257-
}: {
258-
<MultiPhase::<T>>::create_snapshot().unwrap()
259-
} verify {
260-
assert!(<MultiPhase<T>>::snapshot().is_some());
261-
}
262-
263281
submit {
264282
let c in 1 .. (T::SignedMaxSubmissions::get() - 1);
265283

@@ -307,7 +325,7 @@ frame_benchmarking::benchmarks! {
307325
T::BenchmarkingConfig::DESIRED_TARGETS[1];
308326

309327
let witness = SolutionOrSnapshotSize { voters: v, targets: t };
310-
let raw_solution = solution_with_size::<T>(witness, a, d);
328+
let raw_solution = solution_with_size::<T>(witness, a, d)?;
311329

312330
assert!(<MultiPhase<T>>::queued_solution().is_none());
313331
<CurrentPhase<T>>::put(Phase::Unsigned((true, 1u32.into())));
@@ -324,6 +342,84 @@ frame_benchmarking::benchmarks! {
324342
assert!(<MultiPhase<T>>::queued_solution().is_some());
325343
}
326344

345+
// This is checking a valid solution. The worse case is indeed a valid solution.
346+
feasibility_check {
347+
// number of votes in snapshot.
348+
let v in (T::BenchmarkingConfig::VOTERS[0]) .. T::BenchmarkingConfig::VOTERS[1];
349+
// number of targets in snapshot.
350+
let t in (T::BenchmarkingConfig::TARGETS[0]) .. T::BenchmarkingConfig::TARGETS[1];
351+
// number of assignments, i.e. compact.len(). This means the active nominators, thus must be
352+
// a subset of `v` component.
353+
let a in (T::BenchmarkingConfig::ACTIVE_VOTERS[0]) .. T::BenchmarkingConfig::ACTIVE_VOTERS[1];
354+
// number of desired targets. Must be a subset of `t` component.
355+
let d in (T::BenchmarkingConfig::DESIRED_TARGETS[0]) .. T::BenchmarkingConfig::DESIRED_TARGETS[1];
356+
357+
let size = SolutionOrSnapshotSize { voters: v, targets: t };
358+
let raw_solution = solution_with_size::<T>(size, a, d)?;
359+
360+
assert_eq!(raw_solution.compact.voter_count() as u32, a);
361+
assert_eq!(raw_solution.compact.unique_targets().len() as u32, d);
362+
363+
// encode the most significant storage item that needs to be decoded in the dispatch.
364+
let encoded_snapshot = <MultiPhase<T>>::snapshot().unwrap().encode();
365+
}: {
366+
assert_ok!(<MultiPhase<T>>::feasibility_check(raw_solution, ElectionCompute::Unsigned));
367+
let _decoded_snap = <RoundSnapshot<T::AccountId> as Decode>::decode(&mut &*encoded_snapshot).unwrap();
368+
}
369+
370+
// NOTE: this weight is not used anywhere, but the fact that it should succeed when execution in
371+
// isolation is vital to ensure memory-safety. For the same reason, we don't care about the
372+
// components iterating, we merely check that this operation will work with the "maximum"
373+
// numbers.
374+
//
375+
// ONLY run this benchmark in isolation, and pass the `--extra` flag to enable it.
376+
//
377+
// NOTE: If this benchmark does not run out of memory with a given heap pages, it means that the
378+
// OCW process can SURELY succeed with the given configuration, but the opposite is not true.
379+
// This benchmark is doing more work than a raw call to `OffchainWorker_offchain_worker` runtime
380+
// api call, since it is also setting up some mock data, which will itself exhaust the heap to
381+
// some extent.
382+
#[extra]
383+
mine_solution_offchain_memory {
384+
// number of votes in snapshot. Fixed to maximum.
385+
let v = T::BenchmarkingConfig::MINER_MAXIMUM_VOTERS;
386+
// number of targets in snapshot. Fixed to maximum.
387+
let t = T::BenchmarkingConfig::MAXIMUM_TARGETS;
388+
389+
T::DataProvider::clear();
390+
set_up_data_provider::<T>(v, t);
391+
let now = frame_system::Pallet::<T>::block_number();
392+
<CurrentPhase<T>>::put(Phase::Unsigned((true, now)));
393+
<MultiPhase::<T>>::create_snapshot().unwrap();
394+
}: {
395+
// we can't really verify this as it won't write anything to state, check logs.
396+
<MultiPhase::<T>>::offchain_worker(now)
397+
}
398+
399+
// NOTE: this weight is not used anywhere, but the fact that it should succeed when execution in
400+
// isolation is vital to ensure memory-safety. For the same reason, we don't care about the
401+
// components iterating, we merely check that this operation will work with the "maximum"
402+
// numbers.
403+
//
404+
// ONLY run this benchmark in isolation, and pass the `--extra` flag to enable it.
405+
#[extra]
406+
create_snapshot_memory {
407+
// number of votes in snapshot. Fixed to maximum.
408+
let v = T::BenchmarkingConfig::SNAPSHOT_MAXIMUM_VOTERS;
409+
// number of targets in snapshot. Fixed to maximum.
410+
let t = T::BenchmarkingConfig::MAXIMUM_TARGETS;
411+
412+
T::DataProvider::clear();
413+
set_up_data_provider::<T>(v, t);
414+
assert!(<MultiPhase<T>>::snapshot().is_none());
415+
}: {
416+
<MultiPhase::<T>>::create_snapshot().unwrap()
417+
} verify {
418+
assert!(<MultiPhase<T>>::snapshot().is_some());
419+
assert_eq!(<MultiPhase<T>>::snapshot_metadata().unwrap().voters, v + t);
420+
assert_eq!(<MultiPhase<T>>::snapshot_metadata().unwrap().targets, t);
421+
}
422+
327423
#[extra]
328424
trim_assignments_length {
329425
// number of votes in snapshot.
@@ -344,7 +440,7 @@ frame_benchmarking::benchmarks! {
344440
// Compute a random solution, then work backwards to get the lists of voters, targets, and
345441
// assignments
346442
let witness = SolutionOrSnapshotSize { voters: v, targets: t };
347-
let RawSolution { compact, .. } = solution_with_size::<T>(witness, a, d);
443+
let RawSolution { compact, .. } = solution_with_size::<T>(witness, a, d)?;
348444
let RoundSnapshot { voters, targets } = MultiPhase::<T>::snapshot().unwrap();
349445
let voter_at = helpers::voter_at_fn::<T>(&voters);
350446
let target_at = helpers::target_at_fn::<T>(&targets);
@@ -394,39 +490,10 @@ frame_benchmarking::benchmarks! {
394490
log!(trace, "actual encoded size = {}", encoding.len());
395491
assert!(encoding.len() <= desired_size);
396492
}
397-
398-
// This is checking a valid solution. The worse case is indeed a valid solution.
399-
feasibility_check {
400-
// number of votes in snapshot.
401-
let v in (T::BenchmarkingConfig::VOTERS[0]) .. T::BenchmarkingConfig::VOTERS[1];
402-
// number of targets in snapshot.
403-
let t in (T::BenchmarkingConfig::TARGETS[0]) .. T::BenchmarkingConfig::TARGETS[1];
404-
// number of assignments, i.e. compact.len(). This means the active nominators, thus must be
405-
// a subset of `v` component.
406-
let a in
407-
(T::BenchmarkingConfig::ACTIVE_VOTERS[0]) .. T::BenchmarkingConfig::ACTIVE_VOTERS[1];
408-
// number of desired targets. Must be a subset of `t` component.
409-
let d in
410-
(T::BenchmarkingConfig::DESIRED_TARGETS[0]) ..
411-
T::BenchmarkingConfig::DESIRED_TARGETS[1];
412-
413-
let size = SolutionOrSnapshotSize { voters: v, targets: t };
414-
let raw_solution = solution_with_size::<T>(size, a, d);
415-
416-
assert_eq!(raw_solution.compact.voter_count() as u32, a);
417-
assert_eq!(raw_solution.compact.unique_targets().len() as u32, d);
418-
419-
// encode the most significant storage item that needs to be decoded in the dispatch.
420-
let encoded_snapshot = <MultiPhase<T>>::snapshot().unwrap().encode();
421-
}: {
422-
assert_ok!(<MultiPhase<T>>::feasibility_check(raw_solution, ElectionCompute::Unsigned));
423-
let _decoded_snap = <RoundSnapshot<T::AccountId> as Decode>::decode(&mut &*encoded_snapshot)
424-
.unwrap();
425-
}
426493
}
427494

428495
impl_benchmark_test_suite!(
429496
MultiPhase,
430-
crate::mock::ExtBuilder::default().build(),
497+
crate::mock::ExtBuilder::default().build_offchainify(10).0,
431498
crate::mock::Runtime,
432499
);

0 commit comments

Comments
 (0)