Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
114 commits
Select commit Hold shift + click to select a range
02d79f4
initial impl
shawntabrizi Jun 10, 2022
0c8651c
add template test
shawntabrizi Jun 10, 2022
6571394
linear fit proof size
shawntabrizi Jun 10, 2022
9d22002
always record proof when tracking storage
shawntabrizi Jun 10, 2022
b05dca2
calculate worst case pov
shawntabrizi Jun 11, 2022
04e8d61
remove duplicate worst case
shawntabrizi Jun 11, 2022
1ab7c34
Merge branch 'master' of https://github.com/paritytech/substrate into…
Jun 11, 2022
1d6d2e7
cargo run --quiet --profile=production --features=runtime-benchmarks…
Jun 11, 2022
5d52c9c
more comment output
shawntabrizi Jun 11, 2022
34c4467
Merge branch 'shawntabrizi-pov-in-cli' of https://github.com/parityte…
shawntabrizi Jun 11, 2022
97acc2d
add cli for worst case map size
shawntabrizi Jun 11, 2022
651f347
update name
shawntabrizi Jun 11, 2022
9d0bdf9
clap does not support underscores
shawntabrizi Jun 11, 2022
7c542d8
rename
shawntabrizi Jun 11, 2022
625593e
expose worst case map values
shawntabrizi Jun 12, 2022
dfc55b4
Merge branch 'master' of https://github.com/paritytech/substrate into…
Jun 12, 2022
49c8132
improve some comments
shawntabrizi Jun 12, 2022
baeddb3
cargo run --quiet --profile=production --features=runtime-benchmarks…
Jun 12, 2022
9d11567
Merge branch 'shawntabrizi-pov-in-cli' of https://github.com/parityte…
shawntabrizi Jun 12, 2022
232cb97
Merge branch 'master' into shawntabrizi-pov-in-cli
shawntabrizi Jun 12, 2022
7a9a3d8
update template
shawntabrizi Jun 12, 2022
6a0cf76
Merge branch 'master' of https://github.com/paritytech/substrate into…
Jun 13, 2022
c8cd738
cargo run --quiet --profile=production --features=runtime-benchmarks…
Jun 13, 2022
7c55e48
Merge branch 'master' into shawntabrizi-pov-in-cli
shawntabrizi Jul 10, 2022
e5e549f
Merge branch 'master' into shawntabrizi-pov-in-cli
shawntabrizi Aug 9, 2022
8b272c4
fix fmt
shawntabrizi Aug 9, 2022
f2baa71
more fmt
shawntabrizi Aug 9, 2022
3b44d68
more fmt
shawntabrizi Aug 10, 2022
26daf32
Merge branch 'master' into shawntabrizi-pov-in-cli
shawntabrizi Sep 7, 2022
b6f849b
Merge remote-tracking branch 'origin/master' into shawntabrizi-pov-in…
ggwpez Oct 14, 2022
b5fedc4
Merge remote-tracking branch 'origin/master' into shawntabrizi-pov-in…
ggwpez Dec 2, 2022
a5ddf1f
Dont panic when there is no proof
ggwpez Dec 17, 2022
c0a4bc5
Fix test features
ggwpez Dec 17, 2022
3108f6a
Whitelist :extrinsic_index
ggwpez Dec 17, 2022
8eb47ec
Use whitelist when recording proof
ggwpez Dec 17, 2022
309c0cb
Add logs
ggwpez Dec 17, 2022
9926aef
Add PoV testing pallet
ggwpez Dec 17, 2022
96a4b0d
Deploy PoV testing pallet
ggwpez Dec 17, 2022
c48bcc9
Storage benches reside in the PoV pallet
ggwpez Dec 17, 2022
824785d
Linear regress PoV per component
ggwpez Dec 17, 2022
27a335b
Put PoV into the weight templates
ggwpez Dec 17, 2022
21d89aa
fmt
ggwpez Dec 17, 2022
9e7a9f9
Merge remote-tracking branch 'origin/master' into shawntabrizi-pov-in…
ggwpez Dec 17, 2022
ba83ba6
Extra alanysis choise for PoV
ggwpez Dec 17, 2022
879cf5b
Add+Fix tests
ggwpez Dec 17, 2022
2b19fa1
Make benches faster
ggwpez Dec 17, 2022
b1f89b8
Cleanup
ggwpez Dec 17, 2022
c19e84a
Use same template comments
ggwpez Dec 19, 2022
66a2d9b
Merge remote-tracking branch 'origin/master' into shawntabrizi-pov-in…
ggwpez Dec 19, 2022
9da76e0
".git/.scripts/bench-bot.sh" pallet dev pallet_balances
Dec 19, 2022
cca1f74
".git/.scripts/bench-bot.sh" pallet dev pallet_democracy
Dec 19, 2022
170c358
Update referenda mock BlockWeights
ggwpez Dec 19, 2022
afb4a88
Take measured value size into account
ggwpez Dec 20, 2022
774b383
clippy
ggwpez Dec 20, 2022
43b2cdb
Merge branch 'master' of https://github.com/paritytech/substrate into…
Dec 20, 2022
cd5b536
".git/.scripts/bench-bot.sh" pallet dev pallet_scheduler
Dec 20, 2022
8ddaa2f
WIP
ggwpez Dec 21, 2022
fe5caf9
proof_size: None
ggwpez Dec 21, 2022
c34b538
WIP
ggwpez Dec 22, 2022
d69e0c4
WIP
ggwpez Dec 22, 2022
dc1410f
WIP
ggwpez Dec 22, 2022
906c60e
ugly, but works
ggwpez Dec 22, 2022
1e02140
WIP
ggwpez Dec 22, 2022
5595b55
WIP
ggwpez Dec 22, 2022
3a39968
WIP
ggwpez Dec 22, 2022
74dde1e
wup
ggwpez Dec 22, 2022
61b749e
WIP
ggwpez Dec 22, 2022
2e96402
WIP
ggwpez Dec 22, 2022
3991122
WIP
ggwpez Dec 22, 2022
4c84f87
WIP
ggwpez Dec 22, 2022
3bc2ec5
Add pov_mode attribute to the benchmarks! macro
ggwpez Jan 4, 2023
f2b8c8e
Use pov_mode attribute in PoV benchmarking
ggwpez Jan 4, 2023
b000667
Update tests
ggwpez Jan 4, 2023
8e631b3
Scheduler, Whitelist: Add pov_mode attr
ggwpez Jan 4, 2023
2f3ac23
Update PoV weights
Jan 4, 2023
5acb33a
Add CLI arg: default-pov-mode
ggwpez Jan 4, 2023
e2006ed
Fix tests
ggwpez Jan 4, 2023
21b1c64
fmt
ggwpez Jan 4, 2023
442394a
Merge remote-tracking branch 'server/oty-proof-size-attribute' into s…
ggwpez Jan 4, 2023
0a4829d
fix
ggwpez Jan 4, 2023
697eef7
Revert "Update PoV weights"
ggwpez Jan 4, 2023
95bfa49
Revert "WIP"
ggwpez Jan 4, 2023
467477e
Revert first approach
ggwpez Jan 4, 2023
debbc07
Clippy
ggwpez Jan 4, 2023
213e340
Add extra benchmarks
ggwpez Jan 4, 2023
9fe83c5
Merge branch 'master' of https://github.com/paritytech/substrate into…
Jan 4, 2023
a82f20a
".git/.scripts/commands/bench/bench.sh" pallet dev pallet_alliance
Jan 4, 2023
46fa61a
".git/.scripts/commands/bench/bench.sh" pallet dev pallet_whitelist
Jan 4, 2023
81e6723
".git/.scripts/commands/bench/bench.sh" pallet dev pallet_scheduler
Jan 4, 2023
9ac907d
fmt
ggwpez Jan 4, 2023
637641b
Clippy
ggwpez Jan 4, 2023
9bd0c3d
Clippy 🤦
ggwpez Jan 4, 2023
1b87727
Add reference benchmarks
ggwpez Jan 5, 2023
29b8eed
Fix doc comments
ggwpez Jan 5, 2023
c52210f
Merge remote-tracking branch 'origin/master' into shawntabrizi-pov-in…
Jan 6, 2023
3dd9a4c
Undo logging
ggwpez Jan 7, 2023
44dce06
Merge remote-tracking branch 'origin/master' into shawntabrizi-pov-in…
ggwpez Jan 7, 2023
6bc569b
Merge remote-tracking branch 'origin/master' into shawntabrizi-pov-in…
ggwpez Jan 20, 2023
a4bd72c
Add 'Ignored' pov_mode
ggwpez Jan 20, 2023
52f460b
Allow multiple attributes per benchmark
ggwpez Jan 23, 2023
4ea47d9
Validate pov_mode syntax
ggwpez Jan 23, 2023
8878ab0
Ignore PoV for all contract benchmarks
ggwpez Jan 23, 2023
5af9771
Test
ggwpez Jan 23, 2023
b5438b2
test
ggwpez Jan 23, 2023
545f26f
Bump macro recursion limit
ggwpez Jan 23, 2023
fc33556
fmt
ggwpez Jan 23, 2023
b77fb33
Update contract weights
ggwpez Jan 23, 2023
d988e35
Merge remote-tracking branch 'origin/master' into shawntabrizi-pov-in…
ggwpez Jan 23, 2023
20633ea
fix test ffs
ggwpez Jan 23, 2023
4a6a3e7
pov_mode is unsupported in V2 syntax
ggwpez Jan 23, 2023
f45c2f8
Fix pallet ui tests
ggwpez Jan 23, 2023
74f84d6
update pallet ui
ggwpez Jan 24, 2023
2674325
Fix pallet ui tests
ggwpez Jan 24, 2023
047cf4f
Update weights
ggwpez Jan 25, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Linear regress PoV per component
Splits the PoV calculation into "measured" and "estimated".
The measured part is reported by the Proof recorder and linear
regressed over all components at once.
The estimated part is calculated as worst-case by using the max
PoV size per storage access and calculating one linear regress per
component. This gives each component a (possibly) independent PoV.
For now the measured size will always be lower than the PoV on
Polkadot since it is measured on an empty snapshot. The measured
part is therefor only used as diagnostic for debugging.

Signed-off-by: Oliver Tale-Yazdi <[email protected]>
  • Loading branch information
ggwpez committed Dec 17, 2022
commit 824785d69556aee6a45032ba0abb609caf886331
6 changes: 3 additions & 3 deletions utils/frame/benchmarking-cli/src/pallet/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -534,9 +534,9 @@ impl PalletCmd {
}

if !self.no_storage_info {
let mut comments: Vec<String> = Default::default();
writer::process_storage_results(
&mut comments,
let mut storage_per_prefix = Default::default();
let (comments, _) = writer::process_storage_results(
&mut storage_per_prefix,
&batch.db_results,
storage_info,
self.worst_case_map_values,
Expand Down
169 changes: 118 additions & 51 deletions utils/frame/benchmarking-cli/src/pallet/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
// Outputs benchmark results to Rust files that can be ingested by the runtime.

use std::{
collections::{HashMap, HashSet},
collections::{BTreeSet, HashMap, HashSet},
fs,
path::PathBuf,
};
Expand Down Expand Up @@ -65,12 +65,14 @@ struct BenchmarkData {
#[serde(serialize_with = "string_serialize")]
base_writes: u128,
#[serde(serialize_with = "string_serialize")]
base_proof_size: u128,
base_calculated_proof_size: u128,
#[serde(serialize_with = "string_serialize")]
base_recorded_proof_size: u128,
component_weight: Vec<ComponentSlope>,
component_reads: Vec<ComponentSlope>,
component_writes: Vec<ComponentSlope>,
component_proof_size: Vec<ComponentSlope>,
worst_case_proof_size: u32,
component_calculated_proof_size: Vec<ComponentSlope>,
component_recorded_proof_size: Vec<ComponentSlope>,
component_ranges: Vec<ComponentRange>,
comments: Vec<String>,
#[serde(serialize_with = "string_serialize")]
Expand Down Expand Up @@ -178,9 +180,6 @@ fn get_benchmark_data(
analysis_choice: &AnalysisChoice,
worst_case_map_values: u32,
) -> BenchmarkData {
// You can use this to put any additional comments with the benchmarking output.
let mut comments = Vec::<String>::new();

// Analyze benchmarks to get the linear regression.
let analysis_function = match analysis_choice {
AnalysisChoice::MinSquares => Analysis::min_squares_iqr,
Expand All @@ -194,7 +193,7 @@ fn get_benchmark_data(
.expect("analysis function should return the number of reads for valid inputs");
let writes = analysis_function(&batch.db_results, BenchmarkSelector::Writes)
.expect("analysis function should return the number of writes for valid inputs");
let proof_size = analysis_function(&batch.db_results, BenchmarkSelector::ProofSize)
let recorded_proof_size = Analysis::median_slopes(&batch.db_results, BenchmarkSelector::ProofSize)
.expect("analysis function should return proof sizes for valid inputs");

// Analysis data may include components that are not used, this filters out anything whose value
Expand All @@ -203,7 +202,8 @@ fn get_benchmark_data(
let mut used_extrinsic_time = Vec::new();
let mut used_reads = Vec::new();
let mut used_writes = Vec::new();
let mut used_proof_size = Vec::new();
let mut used_calculated_proof_size = Vec::<ComponentSlope>::new();
let mut used_recorded_proof_size = Vec::<ComponentSlope>::new();

extrinsic_time
.slopes
Expand Down Expand Up @@ -244,39 +244,78 @@ fn get_benchmark_data(
used_writes.push(ComponentSlope { name: name.clone(), slope, error });
}
});
proof_size
recorded_proof_size
.slopes
.into_iter()
.zip(proof_size.names.iter())
.zip(extract_errors(&proof_size.model))
.zip(recorded_proof_size.names.iter())
.zip(extract_errors(&recorded_proof_size.errors))
.for_each(|((slope, name), error)| {
if !slope.is_zero() {
if !used_components.contains(&name) {
used_components.push(name);
}
used_proof_size.push(ComponentSlope { name: name.clone(), slope, error });
// These are only for comments, so don't touch the `used_components`.
used_recorded_proof_size.push(ComponentSlope { name: name.clone(), slope, error });
}
});

// This puts a marker on any component which is entirely unused in the weight formula.
let components = batch.time_results[0]
.components
.iter()
.map(|(name, _)| -> Component {
let name_string = name.to_string();
let is_used = used_components.contains(&&name_string);
Component { name: name_string, is_used }
})
.collect::<Vec<_>>();

// We add additional comments showing which storage items were touched.
// We find the worst case proof size, and use that as the final proof size result.
let worst_case_proof_size: u32 = process_storage_results(
&mut comments,
let mut storage_per_prefix = HashMap::<Vec<u8>, Vec<BenchmarkResult>>::new();
let (comments, warnings) = process_storage_results(
&mut storage_per_prefix,
&batch.db_results,
storage_info,
worst_case_map_values,
);
for warning in warnings {
log::warn!("{}", warning);
}

let proof_size_per_components = storage_per_prefix.iter()
.map(|(prefix, results)| {
let proof_size = analysis_function(results, BenchmarkSelector::ProofSize)
.expect("analysis function should return proof sizes for valid inputs");
let slope = proof_size.slopes.into_iter().zip(proof_size.names.iter())
.zip(extract_errors(&proof_size.errors))
.map(|((slope, name), error)|
ComponentSlope { name: name.clone(), slope, error }
)
.collect::<Vec<_>>();
(prefix.clone(), slope, proof_size.base)
})
.collect::<Vec<_>>();

let mut base_calculated_proof_size = 0;
// Sum up the proof sizes per component
for (_, slope, base) in proof_size_per_components.iter() {
base_calculated_proof_size += base;
for component in slope.iter() {
let mut found = false;
for used_component in used_calculated_proof_size.iter_mut() {
if used_component.name == component.name {
used_component.slope += component.slope;
found = true;
break;
}
}
if !found && !component.slope.is_zero() {
if !used_components.contains(&&component.name) {
used_components.push(&component.name);
}
used_calculated_proof_size.push(ComponentSlope { name: component.name.clone(), slope: component.slope, error: component.error });
}
}
}

// This puts a marker on any component which is entirely unused in the weight formula.
let components = batch.time_results[0]
.components
.iter()
.map(|(name, _)| -> Component {
let name_string = name.to_string();
let is_used = used_components.contains(&&name_string);
Component { name: name_string, is_used }
})
.collect::<Vec<_>>();

let component_ranges = component_ranges
.get(&(batch.pallet.clone(), batch.benchmark.clone()))
.map(|c| c.clone())
Expand All @@ -288,12 +327,13 @@ fn get_benchmark_data(
base_weight: extrinsic_time.base,
base_reads: reads.base,
base_writes: writes.base,
base_proof_size: proof_size.base,
base_calculated_proof_size: base_calculated_proof_size,
base_recorded_proof_size: recorded_proof_size.base,
component_weight: used_extrinsic_time,
component_reads: used_reads,
component_writes: used_writes,
component_proof_size: used_proof_size,
worst_case_proof_size,
component_calculated_proof_size: used_calculated_proof_size,
component_recorded_proof_size: used_recorded_proof_size,
component_ranges,
comments,
min_execution_time: extrinsic_time.minimum,
Expand Down Expand Up @@ -413,13 +453,14 @@ pub(crate) fn write_results(
// from the pallets, and creates comments with information about the storage keys touched during
// each benchmark.
//
// It returns the max PoV size used by all the storage accesses from these results.
// It returns (comments, warnings) for human consumption.
pub(crate) fn process_storage_results(
comments: &mut Vec<String>,
storage_per_prefix: &mut HashMap<Vec<u8>, Vec<BenchmarkResult>>,
results: &[BenchmarkResult],
storage_info: &[StorageInfo],
worst_case_map_values: u32,
) -> u32 {
) -> (Vec<String>, Vec<String>) {
let (mut comments, mut warnings) = (Vec::new(), BTreeSet::new());
let mut storage_info_map = storage_info
.iter()
.map(|info| (info.prefix.clone(), info))
Expand Down Expand Up @@ -449,18 +490,35 @@ pub(crate) fn process_storage_results(
let mut identified_prefix = HashSet::<Vec<u8>>::new();
let mut identified_key = HashSet::<Vec<u8>>::new();

let mut max_pov: u32 = 0;

for result in results {
// We have to iterate in reverse order to catch the largest values for read/write since the
// components start low and then increase and only the first value is used.
for result in results.iter().rev() {
for (key, reads, writes, whitelisted) in &result.keys {
// skip keys which are whitelisted
if *whitelisted {
continue
}

let prefix_length = key.len().min(32);
let prefix = key[0..prefix_length].to_vec();
let is_key_identified = identified_key.contains(key);
let is_prefix_identified = identified_prefix.contains(&prefix);

let mut prefix_result = result.clone();
let key_info = storage_info_map.get(&prefix);
// Use the mathematical worst case, if any. The only case where this is not possible is
// for unbounded storage items, for which we use the measured "best effort" value.
if let Some(key_info) = key_info {
if let Some(max_pov_per_component) = worst_case_pov(
key_info.max_values,
key_info.max_size,
true,
worst_case_map_values,
) {
prefix_result.proof_size = *reads * max_pov_per_component;
}
}
storage_per_prefix.entry(prefix.clone()).or_default().push(prefix_result);

match (is_key_identified, is_prefix_identified) {
// We already did everything, move on...
Expand All @@ -481,7 +539,7 @@ pub(crate) fn process_storage_results(
// For any new prefix, we should write some comment about the number of reads and
// writes.
if !is_prefix_identified {
match storage_info_map.get(&prefix) {
match key_info {
Some(key_info) => {
let comment = format!(
"Storage: {} {} (r:{} w:{})",
Expand All @@ -508,7 +566,7 @@ pub(crate) fn process_storage_results(

// For any new key, we should add the PoV impact.
if !is_key_identified {
match storage_info_map.get(&prefix) {
match key_info {
Some(key_info) => {
match worst_case_pov(
key_info.max_values,
Expand All @@ -517,7 +575,6 @@ pub(crate) fn process_storage_results(
worst_case_map_values,
) {
Some(new_pov) => {
max_pov += new_pov;
let comment = format!(
"Proof: {} {} (max_values: {:?}, max_size: {:?}, added: {})",
String::from_utf8(key_info.pallet_name.clone())
Expand All @@ -531,16 +588,19 @@ pub(crate) fn process_storage_results(
comments.push(comment)
},
None => {
let pallet = String::from_utf8(key_info.pallet_name.clone())
.expect("encoded from string");
let item = String::from_utf8(key_info.storage_name.clone())
.expect("encoded from string");
let comment = format!(
"Proof Skipped: {} {} (max_values: {:?}, max_size: {:?})",
String::from_utf8(key_info.pallet_name.clone())
.expect("encoded from string"),
String::from_utf8(key_info.storage_name.clone())
.expect("encoded from string"),
pallet,
item,
key_info.max_values,
key_info.max_size,
);
comments.push(comment)
comments.push(comment);
warnings.insert(format!("No worst-case PoV size for unbounded item {}::{}", pallet, item));
},
}
},
Expand All @@ -558,11 +618,15 @@ pub(crate) fn process_storage_results(
}
}

max_pov
(comments, warnings.into_iter().collect())
}

// Given the max values and max size of some storage item, calculate the worst
// case PoV.
/// Given the max values and max size of some storage item, calculate the worst
/// case PoV.
///
/// # Arguments
/// * `max_values`: The maximum number of values in the storage item. `None` for unbounded items.
/// * `max_size`: The maximum size of the value in the storage. `None` for unbounded items.
fn worst_case_pov(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would like to see more documentation on this as the logic is not trivial to follow.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a basic example walk-through might be good.

max_values: Option<u32>,
max_size: Option<u32>,
Expand All @@ -573,8 +637,11 @@ fn worst_case_pov(
let trie_size: u32 = if is_new_prefix {
let max_values = max_values.unwrap_or(worst_case_map_values);
let depth: u32 = easy_log_16(max_values);
// 16 items per depth layer, each containing a 32 byte hash.
depth * 16 * 32
// Normally we have 16 entries of 32 byte hashes per tree layer. In the new trie
// layout the hashes are prefixed by their compact length, hence 33 instead. The proof
// compaction can compress one node per layer since we send the value itself,
// therefore we end up with a size of `15 * 33` per layer.
depth * 15 * 33
} else {
0
};
Expand Down