-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Add execution overhead benchmarking #10977
Changes from 21 commits
9ce7f32
f07941f
60d2ebd
5fb334b
8a3a8a8
8928d9f
6631602
e683edb
3f2eaf4
a4ec657
a73644b
e004a5d
48bc8fd
b935d34
aede827
902cae3
316209b
3b5e897
b40b650
9f1659b
e6acb0c
0ed8303
a440311
0878563
6076428
59f478e
533667c
a2c788a
fc9f675
7c6b2c0
2bca7bb
cf2e763
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
// This file is part of Substrate. | ||
|
||
// Copyright (C) 2022 Parity Technologies (UK) Ltd. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
// 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. | ||
|
||
//! Contains the core benchmarking logic. | ||
|
||
use sc_block_builder::{BlockBuilderApi, BlockBuilderProvider}; | ||
use sc_cli::{Error, Result}; | ||
use sc_client_api::Backend as ClientBackend; | ||
use sp_api::{ApiExt, BlockId, Core, ProvideRuntimeApi}; | ||
use sp_blockchain::{ | ||
ApplyExtrinsicFailed::Validity, | ||
Error::{ApplyExtrinsicFailed, RuntimeApiError}, | ||
}; | ||
use sp_runtime::{ | ||
traits::{Block as BlockT, Zero}, | ||
transaction_validity::{InvalidTransaction, TransactionValidityError}, | ||
OpaqueExtrinsic, | ||
}; | ||
|
||
use clap::Args; | ||
use log::info; | ||
use serde::Serialize; | ||
use std::{marker::PhantomData, sync::Arc, time::Instant}; | ||
|
||
use crate::{overhead::cmd::ExtrinsicBuilder, storage::record::Stats}; | ||
|
||
/// Parameters to configure a *overhead* benchmark. | ||
#[derive(Debug, Default, Serialize, Clone, PartialEq, Args)] | ||
pub struct BenchmarkParams { | ||
/// Rounds of warmups before measuring. | ||
#[clap(long, default_value = "100")] | ||
pub warmup: u32, | ||
|
||
/// How many times the benchmark should be repeated. | ||
#[clap(long, default_value = "1000")] | ||
pub repeat: u32, | ||
} | ||
|
||
/// The results of multiple runs in ns. | ||
pub(crate) type BenchRecord = Vec<u64>; | ||
|
||
/// Type of a benchmark. | ||
#[derive(Serialize, Clone, PartialEq)] | ||
ggwpez marked this conversation as resolved.
Show resolved
Hide resolved
|
||
pub(crate) enum BenchmarkType { | ||
/// Measure the per-extrinsic execution overhead. | ||
Extrinsic, | ||
/// Measure the per-block execution overhead. | ||
Block, | ||
} | ||
|
||
/// Holds all objects needed to run the *overhead* benchmarks. | ||
pub(crate) struct Benchmark<Block, BA, C> { | ||
client: Arc<C>, | ||
params: BenchmarkParams, | ||
inherent_data: sp_inherents::InherentData, | ||
ext_builder: Arc<dyn ExtrinsicBuilder>, | ||
_p: PhantomData<(Block, BA)>, | ||
} | ||
|
||
impl<Block, BA, C> Benchmark<Block, BA, C> | ||
where | ||
Block: BlockT<Extrinsic = OpaqueExtrinsic>, | ||
BA: ClientBackend<Block>, | ||
C: BlockBuilderProvider<BA, Block, C> + ProvideRuntimeApi<Block>, | ||
C::Api: ApiExt<Block, StateBackend = BA::State> + BlockBuilderApi<Block>, | ||
{ | ||
/// Create a new [`Self`] from the arguments. | ||
pub fn new( | ||
client: Arc<C>, | ||
params: BenchmarkParams, | ||
inherent_data: sp_inherents::InherentData, | ||
ext_builder: Arc<dyn ExtrinsicBuilder>, | ||
) -> Self { | ||
Self { client, params, inherent_data, ext_builder, _p: PhantomData } | ||
} | ||
|
||
/// Run the specified benchmark. | ||
pub fn bench(&self, bench_type: BenchmarkType) -> Result<Stats> { | ||
let (block, num_ext) = self.build_block(bench_type.clone())?; | ||
ggwpez marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let record = self.measure_block(&block, num_ext, bench_type)?; | ||
Stats::new(&record) | ||
} | ||
|
||
/// Builds a block for the given benchmark type. | ||
/// | ||
/// Returns the block and the number of extrinsics in the block. | ||
fn build_block(&self, bench_type: BenchmarkType) -> Result<(Block, u64)> { | ||
let mut builder = self.client.new_block(Default::default())?; | ||
// Create and insert the inherents. | ||
let inherents = builder.create_inherents(self.inherent_data.clone())?; | ||
for inherent in inherents { | ||
builder.push(inherent)?; | ||
} | ||
|
||
// Return early if we just want an empty block. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I mean it is not empty :P There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed to // Return early if we just want a block with inherents and no additional extrinsics. |
||
if bench_type == BenchmarkType::Block { | ||
return Ok((builder.build()?.block, 0)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And this should return There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since I want to count the inherents as overhead, they should not be counted as payload extrinsics. |
||
} | ||
|
||
// Put as many extrinsics into the block as possible and count them. | ||
info!("Building block, this takes some time..."); | ||
let mut num_ext = 0; | ||
for nonce in 0.. { | ||
let ext = self.ext_builder.remark(nonce).ok_or("Could not build extrinsic")?; | ||
match builder.push(ext.clone()) { | ||
Ok(_) => {}, | ||
ggwpez marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Err(ApplyExtrinsicFailed(Validity(TransactionValidityError::Invalid( | ||
InvalidTransaction::ExhaustsResources, | ||
)))) => break, | ||
Err(error) => panic!("{}", error), | ||
} | ||
num_ext += 1; | ||
} | ||
assert!(num_ext != 0, "A Block must hold at least one extrinsic"); | ||
ggwpez marked this conversation as resolved.
Show resolved
Hide resolved
|
||
info!("Remarks per block: {}", num_ext); | ||
let block = builder.build()?.block; | ||
|
||
Ok((block, num_ext)) | ||
} | ||
|
||
/// Measures the time that it take to execute a block or an extrinsic. | ||
fn measure_block( | ||
&self, | ||
block: &Block, | ||
num_ext: u64, | ||
bench_type: BenchmarkType, | ||
) -> Result<BenchRecord> { | ||
let mut record = BenchRecord::new(); | ||
if bench_type == BenchmarkType::Extrinsic && num_ext == 0 { | ||
return Err("Cannot measure the extrinsic time of an empty block".into()) | ||
} | ||
let genesis = BlockId::Number(Zero::zero()); | ||
|
||
info!("Running {} warmups...", self.params.warmup); | ||
for _ in 0..self.params.warmup { | ||
self.client | ||
.runtime_api() | ||
.execute_block(&genesis, block.clone()) | ||
.map_err(|e| Error::Client(RuntimeApiError(e)))?; | ||
} | ||
|
||
info!("Executing block {} times", self.params.repeat); | ||
// Interesting part here: | ||
// Execute a block multiple times and record each execution time. | ||
for _ in 0..self.params.repeat { | ||
let block = block.clone(); | ||
let start = Instant::now(); | ||
self.client | ||
.runtime_api() | ||
ggwpez marked this conversation as resolved.
Show resolved
Hide resolved
|
||
.execute_block(&genesis, block) | ||
.map_err(|e| Error::Client(RuntimeApiError(e)))?; | ||
|
||
let elapsed = start.elapsed().as_nanos(); | ||
if bench_type == BenchmarkType::Extrinsic { | ||
// Checked for non-zero div above. | ||
record.push(elapsed as u64 / num_ext); | ||
ggwpez marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} else { | ||
record.push(elapsed as u64); | ||
} | ||
} | ||
|
||
Ok(record) | ||
} | ||
} | ||
|
||
impl BenchmarkType { | ||
/// Short name of the benchmark type. | ||
pub(crate) fn short_name(&self) -> &'static str { | ||
match self { | ||
Self::Extrinsic => "extrinsic", | ||
Self::Block => "block", | ||
} | ||
} | ||
|
||
/// Long name of the benchmark type. | ||
pub(crate) fn long_name(&self) -> &'static str { | ||
match self { | ||
Self::Extrinsic => "ExtrinsicBase", | ||
Self::Block => "BlockExecution", | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kicked this feature check out since it is not needed.