Files
wasm-instrument/tests/overhead.rs
T
Sasha Gryaznov a4dde28607 Add new gas metering method: mutable global + local gas function (#34)
* fix misprints in doc comments

* added global gas tracker variable and local gas fn

* all exported functions of the module to accept a new param and to set the gas_left global to its value at their very start

* make module support both gas metering methods

* tests fixed for the old metering method

* better naming

* MutableGlobal metering method implemented, tests for the old method pass

* gas_metering::tests updated and pass

* all tests udpdated and pass

* emacs backup files to .gitignore

* docs updated

* clippy fix

* iff = if and only if

* more clippy

* docs misprints fixes

* refactored to have Backend trait and two implementations in separate sub-modules

* docs updated

* fixed old benches (updating them is coming next)

* added bench for an instrumented wasm-coremark

* updated benches: added them for both gas_metering instrumentations

* benches contest first ver

* added debug prints to the bench

* refactored to better fit frontend-backend pattern

* docs update

* updated benches

* design updated on feedback

* re-structured sub-modules

re-structured sub-modules & updated docs

* docs improved

* addressed latest feedback comments

* re-writed the local gas function

* coremark benches show ~20% performance improvement

* fix ci: test + clippy

* save before re-factoring prepare_in_wasm()

* bare_call_16 shows 16% worse perf

* + fibonacci recursive bench

* refactored benchmarks

* + factorial recursive bench

* benches on wasmi fixtures show no perf improvement, coremark runs ~20% faster being instrumented with mutable_global gas metering

* charge gas for local gas func isntructions execution

* replaced benchmark which requires multi_value feature

* save: optimized gas func a bit (benches work, fixture tests fail)

* 1033% overhead on many_blocks.wasm when mut_global gas_metering together with stack_height

* size overhead test for both gas metering methods + stack limiter

* added more benches

* improved print_size_overhead test

* test for comparing size overheads of two gas_metering injectors

* before optimization: benches + size overhead

* optimization try-1: inline part of gas func instructions: +benches +size overheads

* optimization try-2: inline hot path of gas fn:  +benches +size overheads

* opt try-3: count for gas fn cost on the caller side: +benches +size overhead

* revert to initial version but with static gas fn cost on the caller side: +benches +sizes

* tests fixed

* use newest wasmi 0.20: +benches +docs updated

* use if-else block instead of Return: +benches

* fix tests

* clippy fix

* addressed review comments

* Update changelog

Co-authored-by: Alexander Theißen <alex.theissen@me.com>
2022-11-20 15:00:10 +01:00

172 lines
5.4 KiB
Rust

use std::{
fs::{read, read_dir, ReadDir},
path::PathBuf,
};
use wasm_instrument::{
gas_metering::{self, host_function, mutable_global, ConstantCostRules},
inject_stack_limiter,
parity_wasm::{deserialize_buffer, elements::Module, serialize},
};
fn fixture_dir() -> PathBuf {
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.push("benches");
path.push("fixtures");
path
}
use gas_metering::Backend;
fn gas_metered_mod_len<B: Backend>(orig_module: Module, backend: B) -> (Module, usize) {
let module = gas_metering::inject(orig_module, backend, &ConstantCostRules::default()).unwrap();
let bytes = serialize(module.clone()).unwrap();
let len = bytes.len();
(module, len)
}
fn stack_limited_mod_len(module: Module) -> (Module, usize) {
let module = inject_stack_limiter(module, 128).unwrap();
let bytes = serialize(module.clone()).unwrap();
let len = bytes.len();
(module, len)
}
struct InstrumentedWasmResults {
filename: String,
original_module_len: usize,
stack_limited_len: usize,
gas_metered_host_fn_len: usize,
gas_metered_mut_glob_len: usize,
gas_metered_host_fn_then_stack_limited_len: usize,
gas_metered_mut_glob_then_stack_limited_len: usize,
}
fn size_overheads_all(files: ReadDir) -> Vec<InstrumentedWasmResults> {
files
.map(|entry| {
let entry = entry.unwrap();
let filename = entry.file_name().into_string().unwrap();
let (original_module_len, orig_module) = {
let bytes = match entry.path().extension().unwrap().to_str() {
Some("wasm") => read(&entry.path()).unwrap(),
Some("wat") =>
wat::parse_bytes(&read(&entry.path()).unwrap()).unwrap().into_owned(),
_ => panic!("expected fixture_dir containing .wasm or .wat files only"),
};
let len = bytes.len();
let module: Module = deserialize_buffer(&bytes).unwrap();
(len, module)
};
let (gm_host_fn_module, gas_metered_host_fn_len) = gas_metered_mod_len(
orig_module.clone(),
host_function::Injector::new("env", "gas"),
);
let (gm_mut_global_module, gas_metered_mut_glob_len) =
gas_metered_mod_len(orig_module.clone(), mutable_global::Injector::new("gas_left"));
let stack_limited_len = stack_limited_mod_len(orig_module).1;
let (_gm_hf_sl_mod, gas_metered_host_fn_then_stack_limited_len) =
stack_limited_mod_len(gm_host_fn_module);
let (_gm_mg_sl_module, gas_metered_mut_glob_then_stack_limited_len) =
stack_limited_mod_len(gm_mut_global_module);
InstrumentedWasmResults {
filename,
original_module_len,
stack_limited_len,
gas_metered_host_fn_len,
gas_metered_mut_glob_len,
gas_metered_host_fn_then_stack_limited_len,
gas_metered_mut_glob_then_stack_limited_len,
}
})
.collect()
}
fn calc_size_overheads() -> Vec<InstrumentedWasmResults> {
let mut wasm_path = fixture_dir();
wasm_path.push("wasm");
let mut wat_path = fixture_dir();
wat_path.push("wat");
let mut results = size_overheads_all(read_dir(wasm_path).unwrap());
let results_wat = size_overheads_all(read_dir(wat_path).unwrap());
results.extend(results_wat);
results
}
/// Print the overhead of applying gas metering, stack
/// height limiting or both.
///
/// Use `cargo test print_size_overhead -- --nocapture`.
#[test]
fn print_size_overhead() {
let mut results = calc_size_overheads();
results.sort_unstable_by(|a, b| {
b.gas_metered_mut_glob_then_stack_limited_len
.cmp(&a.gas_metered_mut_glob_then_stack_limited_len)
});
for r in results {
let filename = r.filename;
let original_size = r.original_module_len / 1024;
let stack_limit = r.stack_limited_len * 100 / r.original_module_len;
let host_fn = r.gas_metered_host_fn_len * 100 / r.original_module_len;
let mut_glob = r.gas_metered_mut_glob_len * 100 / r.original_module_len;
let host_fn_sl = r.gas_metered_host_fn_then_stack_limited_len * 100 / r.original_module_len;
let mut_glob_sl =
r.gas_metered_mut_glob_then_stack_limited_len * 100 / r.original_module_len;
println!(
"{filename:30}: orig = {original_size:4} kb, stack_limiter = {stack_limit} %, \
gas_metered_host_fn = {host_fn} %, both = {host_fn_sl} %,\n \
{:69} gas_metered_mut_global = {mut_glob} %, both = {mut_glob_sl} %",
""
);
}
}
/// Compare module size overhead of applying gas metering with two methods.
///
/// Use `cargo test print_gas_metered_sizes -- --nocapture`.
#[test]
fn print_gas_metered_sizes() {
let overheads = calc_size_overheads();
let mut results = overheads
.iter()
.map(|r| {
let diff = (r.gas_metered_mut_glob_len * 100 / r.gas_metered_host_fn_len) as i32 - 100;
(diff, r)
})
.collect::<Vec<(i32, &InstrumentedWasmResults)>>();
results.sort_unstable_by(|a, b| b.0.cmp(&a.0));
println!(
"| {:28} | {:^16} | gas metered/host fn | gas metered/mut global | size diff |",
"fixture", "original size",
);
println!("|{:-^30}|{:-^18}|{:-^21}|{:-^24}|{:-^11}|", "", "", "", "", "",);
for r in results {
let filename = &r.1.filename;
let original_size = &r.1.original_module_len / 1024;
let host_fn = &r.1.gas_metered_host_fn_len / 1024;
let mut_glob = &r.1.gas_metered_mut_glob_len / 1024;
let host_fn_percent = &r.1.gas_metered_host_fn_len * 100 / r.1.original_module_len;
let mut_glob_percent = &r.1.gas_metered_mut_glob_len * 100 / r.1.original_module_len;
let host_fn = format!("{host_fn} kb ({host_fn_percent:}%)");
let mut_glob = format!("{mut_glob} kb ({mut_glob_percent:}%)");
let diff = &r.0;
println!(
"| {filename:28} | {original_size:13} kb | {host_fn:>19} | {mut_glob:>22} | {diff:+8}% |"
);
}
}