Skip to content
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

test(pallets): add instantiate_module_* benches per section #3929

Merged
merged 8 commits into from
May 16, 2024
122 changes: 119 additions & 3 deletions pallets/gear/src/benchmarking/code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ use gear_wasm_instrument::{
parity_wasm::{
builder,
elements::{
self, BlockType, CustomSection, FuncBody, Instruction, Instructions, Section, ValueType,
self, BlockType, CustomSection, FuncBody, FunctionType, Instruction, Instructions,
Section, Type, ValueType,
},
},
syscalls::SyscallName,
Expand Down Expand Up @@ -69,6 +70,8 @@ pub struct ModuleDefinition {
pub data_segments: Vec<DataSegment>,
/// Creates the supplied amount of i64 mutable globals initialized with random values.
pub num_globals: u32,
/// Make i64 globals init expr occupy 9 bytes.
pub full_length_globals: bool,
/// List of syscalls that the module should import. They start with index 0.
pub imported_functions: Vec<SyscallName>,
/// Function body of the exported `init` function. Body is empty if `None`.
Expand All @@ -90,6 +93,8 @@ pub struct ModuleDefinition {
pub aux_res: Option<ValueType>,
/// Create a table containing function pointers.
pub table: Option<TableSegment>,
/// Create a type section with the specified amount of types.
pub types: Option<TypeSegment>,
/// Create a section named "dummy" of the specified size. This is useful in order to
/// benchmark the overhead of loading and storing codes of specified sizes. The dummy
/// section only contributes to the size of the program but does not affect execution.
Expand All @@ -106,6 +111,10 @@ pub struct TableSegment {
pub function_index: u32,
}

pub struct TypeSegment {
pub num_elements: u32,
}

pub struct DataSegment {
pub offset: u32,
pub value: Vec<u8>,
Expand Down Expand Up @@ -261,7 +270,11 @@ where
if def.num_globals > 0 {
use rand::{distributions::Standard, prelude::*};
let rng = rand_pcg::Pcg32::seed_from_u64(3112244599778833558);
for val in rng.sample_iter(Standard).take(def.num_globals as usize) {
for mut val in rng.sample_iter(Standard).take(def.num_globals as usize) {
// Make i64 const init expr use full length
if def.full_length_globals {
val |= 1 << 63;
}
program = program
.global()
.value_type()
Expand Down Expand Up @@ -315,7 +328,24 @@ where
)));
}

let code = program.build();
let mut code = program.build();

// Add dummy type section
if let Some(types) = def.types {
for section in code.sections_mut() {
if let Section::Type(sec) = section {
for _ in 0..types.num_elements {
sec.types_mut().push(Type::Function(FunctionType::new(
breathx marked this conversation as resolved.
Show resolved Hide resolved
vec![ValueType::I64; 6],
vec![ValueType::I64; 1],
)));
}
// Add the types only to the first type section
break;
}
}
}

let code = code.into_bytes().unwrap();
let hash = CodeId::generate(&code);
Self {
Expand Down Expand Up @@ -378,6 +408,92 @@ where
module.into()
}

/// Creates a WebAssembly module with a data section of size `data_section_bytes`.
/// The generated module contains `data_segment_num` data segments with an overall size of `data_section_bytes`.
/// If `data_segment_num` is 0, no data segments are added.
/// If the result of dividing `data_section_bytes` by `data_segment_num` is 0, zero-length data segments are added.
pub fn sized_data_section(data_section_bytes: u32, data_segment_num: u32) -> Self {
let mut module = ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()),
..Default::default()
};

if data_segment_num != 0 {
let (data_segment_size, residual_bytes) = (
data_section_bytes / data_segment_num,
data_section_bytes % data_segment_num,
);

for seg_idx in 0..data_segment_num {
module.data_segments.push(DataSegment {
offset: seg_idx * data_segment_size,
value: vec![0xA5; data_segment_size as usize],
});
}

// Add residual bytes to the last data segment
if residual_bytes != 0 {
if let Some(last) = module.data_segments.last_mut() {
last.value
.resize(data_segment_size as usize + residual_bytes as usize, 0xA5)
}
}
}

module.into()
}

/// Creates a wasm module of `target_bytes` size.
/// The generated module generates wasm module containing only global section.
pub fn sized_global_section(target_bytes: u32) -> Self {
let mut module = ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()),
..Default::default()
};

// Maximum size of encoded i64 global is 14 bytes.
module.num_globals = target_bytes / 14;
module.full_length_globals = true;

module.into()
}

/// Creates a WebAssembly module with a table size of `target_bytes` bytes.
/// Each element in the table points to function index `0` and occupies 1 byte.
pub fn sized_table_section(target_bytes: u32) -> Self {
ByteNacked marked this conversation as resolved.
Show resolved Hide resolved
let mut module = ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()),
..Default::default()
};

module.init_body = Some(body::empty());

// 1 element with function index value `0` takes 1 byte to encode.
let num_elements = target_bytes;

module.table = Some(TableSegment {
num_elements,
function_index: 0,
});

module.into()
}

/// Creates a WebAssembly module with a type section of size `target_bytes` bytes.
pub fn sized_type_section(target_bytes: u32) -> Self {
let mut module = ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()),
..Default::default()
};

// Dummy type section takes 10 bytes.
module.types = Some(TypeSegment {
num_elements: target_bytes / 10,
});

module.into()
}

/// Creates a memory instance for use in a sandbox with dimensions declared in this module
/// and adds it to `env`. A reference to that memory is returned so that it can be used to
/// access the memory contents from the supervisor.
Expand Down
45 changes: 43 additions & 2 deletions pallets/gear/src/benchmarking/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ const MAX_PAYLOAD_LEN: u32 = 32 * 64 * 1024;
const MAX_PAYLOAD_LEN_KB: u32 = MAX_PAYLOAD_LEN / 1024;
const MAX_PAGES: u32 = 512;
const MAX_SALT_SIZE_BYTES: u32 = 4 * 1024 * 1024;
const MAX_NUMBER_OF_DATA_SEGMENTS: u32 = 1024;
ByteNacked marked this conversation as resolved.
Show resolved Hide resolved

/// How many batches we do per API benchmark.
const API_BENCHMARK_BATCHES: u32 = 20;
Expand Down Expand Up @@ -374,13 +375,53 @@ benchmarks! {
BenchmarkStorage::<T>::get(c).expect("Infallible: Key not found in storage");
}

// `c`: Size of the code in kilobytes.
instantiate_module_per_kb {
// `c`: Size of the code section in kilobytes.
instantiate_module_code_section_per_kb {
let c in 0 .. T::Schedule::get().limits.code_len / 1024;

let WasmModule { code, .. } = WasmModule::<T>::sized(c * 1024, Location::Init);
let ext = Externalities::new(ProcessorContext::new_mock());
}: {
Environment::new(ext, &code, DispatchKind::Init, Default::default(), max_pages::<T>().into()).unwrap();
}

// `d`: Size of the data section in kilobytes.
instantiate_module_data_section_per_kb {
let d in 0 .. T::Schedule::get().limits.code_len / 1024;

let WasmModule { code, .. } = WasmModule::<T>::sized_data_section(d * 1024, MAX_NUMBER_OF_DATA_SEGMENTS);
let ext = Externalities::new(ProcessorContext::new_mock());
breathx marked this conversation as resolved.
Show resolved Hide resolved
}: {
Environment::new(ext, &code, DispatchKind::Init, Default::default(), max_pages::<T>().into()).unwrap();
}

// `g`: Size of the global section in kilobytes.
instantiate_module_global_section_per_kb {
let g in 0 .. T::Schedule::get().limits.code_len / 1024;

let WasmModule { code, .. } = WasmModule::<T>::sized_global_section(g * 1024);
let ext = Externalities::new(ProcessorContext::new_mock());
}: {
Environment::new(ext, &code, DispatchKind::Init, Default::default(), max_pages::<T>().into()).unwrap();
}

// `t`: Size of the table section in kilobytes.
instantiate_module_table_section_per_kb {
let t in 0 .. T::Schedule::get().limits.code_len / 1024;

let WasmModule { code, .. } = WasmModule::<T>::sized_table_section(t * 1024);
let ext = Externalities::new(ProcessorContext::new_mock());
}: {
Environment::new(ext, &code, DispatchKind::Init, Default::default(), max_pages::<T>().into()).unwrap();
}

// `t`: Size of the type section in kilobytes.
instantiate_module_type_section_per_kb {
let t in 0 .. T::Schedule::get().limits.code_len / 1024;

let WasmModule { code, .. } = WasmModule::<T>::sized_type_section(t * 1024);
let ext = Externalities::new(ProcessorContext::new_mock());
}: {
Environment::new(ext, &code, DispatchKind::Init, Default::default(), max_pages::<T>().into()).unwrap();
}

Expand Down
4 changes: 3 additions & 1 deletion pallets/gear/src/schedule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -763,7 +763,9 @@ impl<T: Config> Default for Schedule<T> {
memory_weights: Default::default(),
db_write_per_byte: to_weight!(cost_byte!(db_write_per_kb)),
db_read_per_byte: to_weight!(cost_byte!(db_read_per_kb)),
module_instantiation_per_byte: to_weight!(cost_byte!(instantiate_module_per_kb)),
module_instantiation_per_byte: to_weight!(cost_byte!(
instantiate_module_code_section_per_kb
)),
code_instrumentation_cost: call_zero!(reinstrument_per_kb, 0),
code_instrumentation_byte_cost: to_weight!(cost_byte!(reinstrument_per_kb)),
}
Expand Down
99 changes: 96 additions & 3 deletions pallets/gear/src/weights.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading