-
Notifications
You must be signed in to change notification settings - Fork 1.1k
pallet-revive: Fix the contract size related benchmarks #7568
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
8fea89f
2996e66
65f4668
2634eb6
2b6ecd0
4c61a4d
dd17823
91b8326
eafd837
88a0ce9
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,24 @@ | ||
| title: 'pallet-revive: Fix the contract size related benchmarks' | ||
| doc: | ||
| - audience: Runtime Dev | ||
| description: |- | ||
| Partly addresses https://github.com/paritytech/polkadot-sdk/issues/6157 | ||
|
|
||
| The benchmarks measuring the impact of contract sizes on calling or instantiating a contract were bogus because they needed to be written in assembly in order to tightly control the basic block size. | ||
|
|
||
| This fixes the benchmarks for: | ||
| - call_with_code_per_byte | ||
| - upload_code | ||
| - instantiate_with_code | ||
|
|
||
| And adds a new benchmark that accounts for the fact that the interpreter will always compile whole basic blocks: | ||
| - basic_block_compilation | ||
|
|
||
| After this PR only the weight we assign to instructions need to be addressed. | ||
| crates: | ||
| - name: pallet-revive | ||
| bump: major | ||
| - name: pallet-revive-fixtures | ||
| bump: major | ||
| - name: pallet-revive-uapi | ||
| bump: major |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,7 +24,8 @@ | |
| //! we define this simple definition of a contract that can be passed to `create_code` that | ||
| //! compiles it down into a `WasmModule` that can be used as a contract's code. | ||
|
|
||
| use alloc::vec::Vec; | ||
| use crate::limits; | ||
| use alloc::{fmt::Write, string::ToString, vec::Vec}; | ||
| use pallet_revive_fixtures::bench as bench_fixtures; | ||
| use sp_core::H256; | ||
| use sp_io::hashing::keccak_256; | ||
|
|
@@ -47,9 +48,46 @@ impl WasmModule { | |
| Self::new(bench_fixtures::dummy_unique(replace_with)) | ||
| } | ||
|
|
||
| /// A contract code of specified sizte that does nothing. | ||
| pub fn sized(_size: u32) -> Self { | ||
| Self::dummy() | ||
| /// Same as as `with_num_instructions` but based on the blob size. | ||
| /// | ||
| /// This is neeeded when we need to weigh a blob without knowing how much instructions it | ||
| /// contains. | ||
| pub fn sized(size: u32) -> Self { | ||
| // Due to variable length encoding of instructions this is not precise. But we only | ||
| // need rough numbers for our benchmarks. | ||
| Self::with_num_instructions(size / 3) | ||
|
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. Could you please explain why we divide by 3? Is it a minimal instruction size?
Member
Author
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. This is the average size of an instruction in the PolkaVM blob. But since it uses variable size encoding its not excact (see comment above). I played around a little with divisors until i get roughly the amount of instructions to hit the max blob size. |
||
| } | ||
|
|
||
| /// A contract code of specified number of instructions that uses all its bytes for instructions | ||
| /// but will return immediately. | ||
| /// | ||
| /// All the basic blocks are maximum sized (only the first is important though). This is to | ||
| /// account for the fact that the interpreter will compile one basic block at a time even | ||
| /// when no code is executed. Hence this contract will trigger the compilation of a maximum | ||
| /// sized basic block and then return with its first instruction. | ||
| /// | ||
| /// All the code will be put into the "call" export. Hence this code can be safely used for the | ||
| /// `instantiate_with_code` benchmark where no compilation of any block should be measured. | ||
| pub fn with_num_instructions(num_instructions: u32) -> Self { | ||
| let mut text = " | ||
| pub @deploy: | ||
| ret | ||
| pub @call: | ||
| " | ||
| .to_string(); | ||
| for i in 0..num_instructions { | ||
| match i { | ||
| // return execution right away without breaking up basic block | ||
| // SENTINEL is a hard coded syscall that terminates execution | ||
| 0 => write!(text, "ecalli {}\n", crate::SENTINEL).unwrap(), | ||
athei marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| i if i % (limits::code::BASIC_BLOCK_SIZE - 1) == 0 => | ||
| text.push_str("fallthrough\n"), | ||
| _ => text.push_str("a0 = a1 + a2\n"), | ||
| } | ||
| } | ||
| text.push_str("ret\n"); | ||
| let code = polkavm_common::assembler::assemble(&text).unwrap(); | ||
| Self::new(code) | ||
| } | ||
|
|
||
| /// A contract code that calls the "noop" host function in a loop depending in the input. | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.