Fix algorithmic complexity of on-demand scheduler with regards to number of cores. (#3190)

We witnessed really poor performance on Rococo, where we ended up with
50 on-demand cores. This was due to the fact that for each core the full
queue was processed. With this change full queue processing will happen
way less often (most of the time complexity is O(1) or O(log(n))) and if
it happens then only for one core (in expectation).

Also spot price is now updated before each order to ensure economic back
pressure.


TODO:

- [x] Implement
- [x] Basic tests
- [x] Add more tests (see todos)
- [x] Run benchmark to confirm better performance, first results suggest
> 100x faster.
- [x] Write migrations
- [x] Bump scale-info version and remove patch in Cargo.toml
- [x] Write PR docs: on-demand performance improved, more on-demand
cores are now non problematic anymore. If need by also the max queue
size can be increased again. (Maybe not to 10k)

Optional: Performance can be improved even more, if we called
`pop_assignment_for_core()`, before calling `report_processed` (Avoid
needless affinity drops). The effect gets smaller the larger the claim
queue and I would only go for it, if it does not add complexity to the
scheduler.

---------

Co-authored-by: eskimor <eskimor@no-such-url.com>
Co-authored-by: antonva <anton.asgeirsson@parity.io>
Co-authored-by: command-bot <>
Co-authored-by: Anton Vilhelm Ásgeirsson <antonva@users.noreply.github.com>
Co-authored-by: ordian <write@reusable.software>
This commit is contained in:
eskimor
2024-03-20 14:53:55 +01:00
committed by GitHub
parent b686bfefba
commit b74353d3e9
13 changed files with 1051 additions and 551 deletions
@@ -16,11 +16,11 @@
//! Autogenerated weights for `runtime_parachains::assigner_on_demand`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
//! DATE: 2023-08-11, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0
//! DATE: 2024-03-18, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `runner-fljshgub-project-163-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("rococo-dev")`, DB CACHE: 1024
//! HOSTNAME: `runner-h2rr8wx7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("westend-dev")`, DB CACHE: 1024
// Executed Command:
// target/production/polkadot
@@ -31,11 +31,11 @@
// --extrinsic=*
// --wasm-execution=compiled
// --heap-pages=4096
// --json-file=/builds/parity/mirrors/polkadot/.git/.artifacts/bench.json
// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json
// --pallet=runtime_parachains::assigner_on_demand
// --chain=rococo-dev
// --header=./file_header.txt
// --output=./runtime/rococo/src/weights/
// --chain=westend-dev
// --header=./polkadot/file_header.txt
// --output=./polkadot/runtime/westend/src/weights/
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
@@ -48,44 +48,44 @@ use core::marker::PhantomData;
/// Weight functions for `runtime_parachains::assigner_on_demand`.
pub struct WeightInfo<T>(PhantomData<T>);
impl<T: frame_system::Config> runtime_parachains::assigner_on_demand::WeightInfo for WeightInfo<T> {
/// Storage: `OnDemandAssignmentProvider::SpotTraffic` (r:1 w:0)
/// Proof: `OnDemandAssignmentProvider::SpotTraffic` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `Paras::ParaLifecycles` (r:1 w:0)
/// Proof: `Paras::ParaLifecycles` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `OnDemandAssignmentProvider::OnDemandQueue` (r:1 w:1)
/// Proof: `OnDemandAssignmentProvider::OnDemandQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `OnDemandAssignmentProvider::QueueStatus` (r:1 w:1)
/// Proof: `OnDemandAssignmentProvider::QueueStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `OnDemandAssignmentProvider::ParaIdAffinity` (r:1 w:0)
/// Proof: `OnDemandAssignmentProvider::ParaIdAffinity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `OnDemandAssignmentProvider::FreeEntries` (r:1 w:1)
/// Proof: `OnDemandAssignmentProvider::FreeEntries` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// The range of component `s` is `[1, 9999]`.
fn place_order_keep_alive(s: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `297 + s * (4 ±0)`
// Estimated: `3762 + s * (4 ±0)`
// Minimum execution time: 33_522_000 picoseconds.
Weight::from_parts(35_436_835, 0)
.saturating_add(Weight::from_parts(0, 3762))
// Standard Error: 129
.saturating_add(Weight::from_parts(14_041, 0).saturating_mul(s.into()))
// Measured: `218 + s * (8 ±0)`
// Estimated: `3681 + s * (8 ±0)`
// Minimum execution time: 21_396_000 picoseconds.
Weight::from_parts(20_585_695, 0)
.saturating_add(Weight::from_parts(0, 3681))
// Standard Error: 127
.saturating_add(Weight::from_parts(20_951, 0).saturating_mul(s.into()))
.saturating_add(T::DbWeight::get().reads(3))
.saturating_add(T::DbWeight::get().writes(1))
.saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into()))
.saturating_add(T::DbWeight::get().writes(2))
.saturating_add(Weight::from_parts(0, 8).saturating_mul(s.into()))
}
/// Storage: `OnDemandAssignmentProvider::SpotTraffic` (r:1 w:0)
/// Proof: `OnDemandAssignmentProvider::SpotTraffic` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `Paras::ParaLifecycles` (r:1 w:0)
/// Proof: `Paras::ParaLifecycles` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `OnDemandAssignmentProvider::OnDemandQueue` (r:1 w:1)
/// Proof: `OnDemandAssignmentProvider::OnDemandQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `OnDemandAssignmentProvider::QueueStatus` (r:1 w:1)
/// Proof: `OnDemandAssignmentProvider::QueueStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `OnDemandAssignmentProvider::ParaIdAffinity` (r:1 w:0)
/// Proof: `OnDemandAssignmentProvider::ParaIdAffinity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `OnDemandAssignmentProvider::FreeEntries` (r:1 w:1)
/// Proof: `OnDemandAssignmentProvider::FreeEntries` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// The range of component `s` is `[1, 9999]`.
fn place_order_allow_death(s: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `297 + s * (4 ±0)`
// Estimated: `3762 + s * (4 ±0)`
// Minimum execution time: 33_488_000 picoseconds.
Weight::from_parts(34_848_934, 0)
.saturating_add(Weight::from_parts(0, 3762))
// Standard Error: 143
.saturating_add(Weight::from_parts(14_215, 0).saturating_mul(s.into()))
// Measured: `218 + s * (8 ±0)`
// Estimated: `3681 + s * (8 ±0)`
// Minimum execution time: 21_412_000 picoseconds.
Weight::from_parts(19_731_554, 0)
.saturating_add(Weight::from_parts(0, 3681))
// Standard Error: 128
.saturating_add(Weight::from_parts(21_055, 0).saturating_mul(s.into()))
.saturating_add(T::DbWeight::get().reads(3))
.saturating_add(T::DbWeight::get().writes(1))
.saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into()))
.saturating_add(T::DbWeight::get().writes(2))
.saturating_add(Weight::from_parts(0, 8).saturating_mul(s.into()))
}
}