This repository was archived by the owner on Nov 15, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Add Proof Size to Weight Output #11637
Merged
Merged
Changes from 1 commit
Commits
Show all changes
114 commits
Select commit
Hold shift + click to select a range
02d79f4
initial impl
shawntabrizi 0c8651c
add template test
shawntabrizi 6571394
linear fit proof size
shawntabrizi 9d22002
always record proof when tracking storage
shawntabrizi b05dca2
calculate worst case pov
shawntabrizi 04e8d61
remove duplicate worst case
shawntabrizi 1ab7c34
Merge branch 'master' of https://github.com/paritytech/substrate into…
1d6d2e7
cargo run --quiet --profile=production --features=runtime-benchmarks…
5d52c9c
more comment output
shawntabrizi 34c4467
Merge branch 'shawntabrizi-pov-in-cli' of https://github.com/parityte…
shawntabrizi 97acc2d
add cli for worst case map size
shawntabrizi 651f347
update name
shawntabrizi 9d0bdf9
clap does not support underscores
shawntabrizi 7c542d8
rename
shawntabrizi 625593e
expose worst case map values
shawntabrizi dfc55b4
Merge branch 'master' of https://github.com/paritytech/substrate into…
49c8132
improve some comments
shawntabrizi baeddb3
cargo run --quiet --profile=production --features=runtime-benchmarks…
9d11567
Merge branch 'shawntabrizi-pov-in-cli' of https://github.com/parityte…
shawntabrizi 232cb97
Merge branch 'master' into shawntabrizi-pov-in-cli
shawntabrizi 7a9a3d8
update template
shawntabrizi 6a0cf76
Merge branch 'master' of https://github.com/paritytech/substrate into…
c8cd738
cargo run --quiet --profile=production --features=runtime-benchmarks…
7c55e48
Merge branch 'master' into shawntabrizi-pov-in-cli
shawntabrizi e5e549f
Merge branch 'master' into shawntabrizi-pov-in-cli
shawntabrizi 8b272c4
fix fmt
shawntabrizi f2baa71
more fmt
shawntabrizi 3b44d68
more fmt
shawntabrizi 26daf32
Merge branch 'master' into shawntabrizi-pov-in-cli
shawntabrizi b6f849b
Merge remote-tracking branch 'origin/master' into shawntabrizi-pov-in…
ggwpez b5fedc4
Merge remote-tracking branch 'origin/master' into shawntabrizi-pov-in…
ggwpez a5ddf1f
Dont panic when there is no proof
ggwpez c0a4bc5
Fix test features
ggwpez 3108f6a
Whitelist :extrinsic_index
ggwpez 8eb47ec
Use whitelist when recording proof
ggwpez 309c0cb
Add logs
ggwpez 9926aef
Add PoV testing pallet
ggwpez 96a4b0d
Deploy PoV testing pallet
ggwpez c48bcc9
Storage benches reside in the PoV pallet
ggwpez 824785d
Linear regress PoV per component
ggwpez 27a335b
Put PoV into the weight templates
ggwpez 21d89aa
fmt
ggwpez 9e7a9f9
Merge remote-tracking branch 'origin/master' into shawntabrizi-pov-in…
ggwpez ba83ba6
Extra alanysis choise for PoV
ggwpez 879cf5b
Add+Fix tests
ggwpez 2b19fa1
Make benches faster
ggwpez b1f89b8
Cleanup
ggwpez c19e84a
Use same template comments
ggwpez 66a2d9b
Merge remote-tracking branch 'origin/master' into shawntabrizi-pov-in…
ggwpez 9da76e0
".git/.scripts/bench-bot.sh" pallet dev pallet_balances
cca1f74
".git/.scripts/bench-bot.sh" pallet dev pallet_democracy
170c358
Update referenda mock BlockWeights
ggwpez afb4a88
Take measured value size into account
ggwpez 774b383
clippy
ggwpez 43b2cdb
Merge branch 'master' of https://github.com/paritytech/substrate into…
cd5b536
".git/.scripts/bench-bot.sh" pallet dev pallet_scheduler
8ddaa2f
WIP
ggwpez fe5caf9
proof_size: None
ggwpez c34b538
WIP
ggwpez d69e0c4
WIP
ggwpez dc1410f
WIP
ggwpez 906c60e
ugly, but works
ggwpez 1e02140
WIP
ggwpez 5595b55
WIP
ggwpez 3a39968
WIP
ggwpez 74dde1e
wup
ggwpez 61b749e
WIP
ggwpez 2e96402
WIP
ggwpez 3991122
WIP
ggwpez 4c84f87
WIP
ggwpez 3bc2ec5
Add pov_mode attribute to the benchmarks! macro
ggwpez f2b8c8e
Use pov_mode attribute in PoV benchmarking
ggwpez b000667
Update tests
ggwpez 8e631b3
Scheduler, Whitelist: Add pov_mode attr
ggwpez 2f3ac23
Update PoV weights
5acb33a
Add CLI arg: default-pov-mode
ggwpez e2006ed
Fix tests
ggwpez 21b1c64
fmt
ggwpez 442394a
Merge remote-tracking branch 'server/oty-proof-size-attribute' into s…
ggwpez 0a4829d
fix
ggwpez 697eef7
Revert "Update PoV weights"
ggwpez 95bfa49
Revert "WIP"
ggwpez 467477e
Revert first approach
ggwpez debbc07
Clippy
ggwpez 213e340
Add extra benchmarks
ggwpez 9fe83c5
Merge branch 'master' of https://github.com/paritytech/substrate into…
a82f20a
".git/.scripts/commands/bench/bench.sh" pallet dev pallet_alliance
46fa61a
".git/.scripts/commands/bench/bench.sh" pallet dev pallet_whitelist
81e6723
".git/.scripts/commands/bench/bench.sh" pallet dev pallet_scheduler
9ac907d
fmt
ggwpez 637641b
Clippy
ggwpez 9bd0c3d
Clippy 🤦
ggwpez 1b87727
Add reference benchmarks
ggwpez 29b8eed
Fix doc comments
ggwpez c52210f
Merge remote-tracking branch 'origin/master' into shawntabrizi-pov-in…
3dd9a4c
Undo logging
ggwpez 44dce06
Merge remote-tracking branch 'origin/master' into shawntabrizi-pov-in…
ggwpez 6bc569b
Merge remote-tracking branch 'origin/master' into shawntabrizi-pov-in…
ggwpez a4bd72c
Add 'Ignored' pov_mode
ggwpez 52f460b
Allow multiple attributes per benchmark
ggwpez 4ea47d9
Validate pov_mode syntax
ggwpez 8878ab0
Ignore PoV for all contract benchmarks
ggwpez 5af9771
Test
ggwpez b5438b2
test
ggwpez 545f26f
Bump macro recursion limit
ggwpez fc33556
fmt
ggwpez b77fb33
Update contract weights
ggwpez d988e35
Merge remote-tracking branch 'origin/master' into shawntabrizi-pov-in…
ggwpez 20633ea
fix test ffs
ggwpez 4a6a3e7
pov_mode is unsupported in V2 syntax
ggwpez f45c2f8
Fix pallet ui tests
ggwpez 74f84d6
update pallet ui
ggwpez 2674325
Fix pallet ui tests
ggwpez 047cf4f
Update weights
ggwpez File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
commit 824785d69556aee6a45032ba0abb609caf886331
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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, | ||
| }; | ||
|
|
@@ -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")] | ||
|
|
@@ -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, | ||
|
|
@@ -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 | ||
|
|
@@ -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 | ||
|
|
@@ -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()) | ||
|
|
@@ -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, | ||
|
|
@@ -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)) | ||
|
|
@@ -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... | ||
|
|
@@ -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:{})", | ||
|
|
@@ -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, | ||
|
|
@@ -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()) | ||
|
|
@@ -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)); | ||
| }, | ||
| } | ||
| }, | ||
|
|
@@ -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( | ||
|
Contributor
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. would like to see more documentation on this as the logic is not trivial to follow.
Contributor
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. a basic example walk-through might be good. |
||
| max_values: Option<u32>, | ||
| max_size: Option<u32>, | ||
|
|
@@ -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 | ||
| }; | ||
|
|
||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.