benchmark interpreter reuse

name               without-reuse.trace ns/iter  with-reuse.trace ns/iter  diff ns/iter   diff %  speedup
 bench_regex_redux  3,214,008                    3,162,089                      -51,919   -1.62%   x 1.02
 bench_rev_comp     7,356,860                    7,168,441                     -188,419   -2.56%   x 1.03
 bench_tiny_keccak  3,943,358                    3,910,487                      -32,871   -0.83%   x 1.01
 fac_opt            10,959                       2,096                           -8,863  -80.87%   x 5.23
 fac_recursive      13,345                       4,277                           -9,068  -67.95%   x 3.12
 recursive_ok       1,190,562                    1,141,940                      -48,622   -4.08%   x 1.04
 recursive_trap     125,047                      111,962                        -13,085  -10.46%   x 1.12
This commit is contained in:
Andrew Dirksen 2018-11-19 15:59:47 -08:00
parent 2f7f809fd6
commit 1913702a03
2 changed files with 332 additions and 26 deletions

View File

@ -8,9 +8,10 @@ extern crate wabt;
use std::error;
use std::fs::File;
use test::Bencher;
use wasmi::{ImportsBuilder, Module, ModuleInstance, NopExternals, RuntimeValue};
use test::Bencher;
mod reuse;
// Load a module from a file.
fn load_from_file(filename: &str) -> Result<Module, Box<error::Error>> {
@ -26,9 +27,8 @@ const REVCOMP_OUTPUT: &'static [u8] = include_bytes!("./revcomp-output.txt");
#[bench]
fn bench_tiny_keccak(b: &mut Bencher) {
let wasm_kernel = load_from_file(
"./wasm-kernel/target/wasm32-unknown-unknown/release/wasm_kernel.wasm",
).expect("failed to load wasm_kernel. Is `build.rs` broken?");
let wasm_kernel = load_from_file("./wasm-kernel/target/wasm32-unknown-unknown/release/wasm_kernel.wasm")
.expect("failed to load wasm_kernel. Is `build.rs` broken?");
let instance = ModuleInstance::new(&wasm_kernel, &ImportsBuilder::default())
.expect("failed to instantiate wasm module")
@ -48,9 +48,8 @@ fn bench_tiny_keccak(b: &mut Bencher) {
#[bench]
fn bench_rev_comp(b: &mut Bencher) {
let wasm_kernel = load_from_file(
"./wasm-kernel/target/wasm32-unknown-unknown/release/wasm_kernel.wasm",
).expect("failed to load wasm_kernel. Is `build.rs` broken?");
let wasm_kernel = load_from_file("./wasm-kernel/target/wasm32-unknown-unknown/release/wasm_kernel.wasm")
.expect("failed to load wasm_kernel. Is `build.rs` broken?");
let instance = ModuleInstance::new(&wasm_kernel, &ImportsBuilder::default())
.expect("failed to instantiate wasm module")
@ -74,7 +73,8 @@ fn bench_rev_comp(b: &mut Bencher) {
);
// Copy test data inside the wasm memory.
let memory = instance.export_by_name("memory")
let memory = instance
.export_by_name("memory")
.expect("Expected export with a name 'memory'")
.as_memory()
.expect("'memory' should be a memory instance")
@ -103,9 +103,8 @@ fn bench_rev_comp(b: &mut Bencher) {
#[bench]
fn bench_regex_redux(b: &mut Bencher) {
let wasm_kernel = load_from_file(
"./wasm-kernel/target/wasm32-unknown-unknown/release/wasm_kernel.wasm",
).expect("failed to load wasm_kernel. Is `build.rs` broken?");
let wasm_kernel = load_from_file("./wasm-kernel/target/wasm32-unknown-unknown/release/wasm_kernel.wasm")
.expect("failed to load wasm_kernel. Is `build.rs` broken?");
let instance = ModuleInstance::new(&wasm_kernel, &ImportsBuilder::default())
.expect("failed to instantiate wasm module")
@ -129,7 +128,8 @@ fn bench_regex_redux(b: &mut Bencher) {
);
// Copy test data inside the wasm memory.
let memory = instance.export_by_name("memory")
let memory = instance
.export_by_name("memory")
.expect("Expected export with a name 'memory'")
.as_memory()
.expect("'memory' should be a memory instance")
@ -148,7 +148,7 @@ fn bench_regex_redux(b: &mut Bencher) {
#[bench]
fn fac_recursive(b: &mut Bencher) {
let wasm = wabt::wat2wasm(
r#"
r#"
;; Recursive factorial
(func (export "fac-rec") (param i64) (result i64)
(if (result i64) (i64.eq (get_local 0) (i64.const 0))
@ -158,7 +158,7 @@ r#"
)
)
)
"#
"#,
).unwrap();
let module = Module::from_buffer(&wasm).unwrap();
@ -168,8 +168,7 @@ r#"
.assert_no_start();
b.iter(|| {
let value = instance
.invoke_export("fac-rec", &[RuntimeValue::I64(25)], &mut NopExternals);
let value = instance.invoke_export("fac-rec", &[RuntimeValue::I64(25)], &mut NopExternals);
assert_matches!(value, Ok(Some(RuntimeValue::I64(7034535277573963776))));
});
}
@ -177,7 +176,7 @@ r#"
#[bench]
fn fac_opt(b: &mut Bencher) {
let wasm = wabt::wat2wasm(
r#"
r#"
;; Optimized factorial.
(func (export "fac-opt") (param i64) (result i64)
(local i64)
@ -192,7 +191,7 @@ r#"
)
(get_local 1)
)
"#
"#,
).unwrap();
let module = Module::from_buffer(&wasm).unwrap();
@ -202,8 +201,7 @@ r#"
.assert_no_start();
b.iter(|| {
let value = instance
.invoke_export("fac-opt", &[RuntimeValue::I64(25)], &mut NopExternals);
let value = instance.invoke_export("fac-opt", &[RuntimeValue::I64(25)], &mut NopExternals);
assert_matches!(value, Ok(Some(RuntimeValue::I64(7034535277573963776))));
});
}
@ -228,7 +226,7 @@ fn recursive_ok(b: &mut Bencher) {
end
)
)
"#
"#,
).unwrap();
let module = Module::from_buffer(&wasm).unwrap();
@ -237,8 +235,7 @@ fn recursive_ok(b: &mut Bencher) {
.assert_no_start();
b.iter(|| {
let value = instance
.invoke_export("call", &[RuntimeValue::I32(8000)], &mut NopExternals);
let value = instance.invoke_export("call", &[RuntimeValue::I32(8000)], &mut NopExternals);
assert_matches!(value, Ok(Some(RuntimeValue::I32(0))));
});
}
@ -262,7 +259,7 @@ fn recursive_trap(b: &mut Bencher) {
unreachable
)
)
"#
"#,
).unwrap();
let module = Module::from_buffer(&wasm).unwrap();
@ -271,8 +268,7 @@ fn recursive_trap(b: &mut Bencher) {
.assert_no_start();
b.iter(|| {
let value = instance
.invoke_export("call", &[RuntimeValue::I32(1000)], &mut NopExternals);
let value = instance.invoke_export("call", &[RuntimeValue::I32(1000)], &mut NopExternals);
assert_matches!(value, Err(_));
});
}

310
benches/src/reuse.rs Normal file
View File

@ -0,0 +1,310 @@
use std::error;
use std::fs::File;
use test::Bencher;
use wasmi::{
FuncInstance, ImportsBuilder, Interpreter, Module, ModuleInstance, NopExternals, RuntimeValue, StackSize,
StackWithLimit,
};
// Load a module from a file.
fn load_from_file(filename: &str) -> Result<Module, Box<error::Error>> {
use std::io::prelude::*;
let mut file = File::open(filename)?;
let mut buf = Vec::new();
file.read_to_end(&mut buf)?;
Ok(Module::from_buffer(buf)?)
}
fn load_kernel() -> Module {
load_from_file("./wasm-kernel/target/wasm32-unknown-unknown/release/wasm_kernel.wasm")
.expect("failed to load wasm_kernel. Is `build.rs` broken?")
}
const REVCOMP_INPUT: &'static [u8] = include_bytes!("./revcomp-input.txt");
const REVCOMP_OUTPUT: &'static [u8] = include_bytes!("./revcomp-output.txt");
fn new_interpreter() -> Interpreter {
let value_stack = StackWithLimit::with_size(StackSize::from_element_count(1024 * 1024));
let call_stack = StackWithLimit::with_size(StackSize::from_element_count(1024 * 1024));
Interpreter::new(value_stack, call_stack)
}
#[bench]
fn bench_tiny_keccak(b: &mut Bencher) {
let wasm_kernel = load_kernel();
let instance = ModuleInstance::new(&wasm_kernel, &ImportsBuilder::default())
.expect("failed to instantiate wasm module")
.assert_no_start();
let test_data_ptr = assert_matches!(
instance.invoke_export("prepare_tiny_keccak", &[], &mut NopExternals),
Ok(Some(v @ RuntimeValue::I32(_))) => v
);
let func = instance.export_by_name("bench_tiny_keccak").unwrap();
let func = func.as_func().unwrap();
let mut interpreter = new_interpreter();
b.iter(|| {
interpreter.reset();
FuncInstance::invoke_configurable(&func, &[test_data_ptr], &mut NopExternals, &mut interpreter).unwrap()
});
}
#[bench]
fn bench_rev_comp(b: &mut Bencher) {
let wasm_kernel = load_kernel();
let instance = ModuleInstance::new(&wasm_kernel, &ImportsBuilder::default())
.expect("failed to instantiate wasm module")
.assert_no_start();
// Allocate buffers for the input and output.
let test_data_ptr: RuntimeValue = {
let input_size = RuntimeValue::I32(REVCOMP_INPUT.len() as i32);
assert_matches!(
instance.invoke_export("prepare_rev_complement", &[input_size], &mut NopExternals),
Ok(Some(v @ RuntimeValue::I32(_))) => v,
"",
)
};
// Get the pointer to the input buffer.
let input_data_mem_offset = assert_matches!(
instance.invoke_export("rev_complement_input_ptr", &[test_data_ptr], &mut NopExternals),
Ok(Some(RuntimeValue::I32(v))) => v as u32,
"",
);
// Copy test data inside the wasm memory.
let memory = instance
.export_by_name("memory")
.expect("Expected export with a name 'memory'")
.as_memory()
.expect("'memory' should be a memory instance")
.clone();
memory
.set(input_data_mem_offset, REVCOMP_INPUT)
.expect("can't load test data into a wasm memory");
let func = instance.export_by_name("bench_rev_complement").unwrap();
let func = func.as_func().unwrap();
let mut interpreter = new_interpreter();
b.iter(|| {
interpreter.reset();
FuncInstance::invoke_configurable(&func, &[test_data_ptr], &mut NopExternals, &mut interpreter).unwrap()
});
// Verify the result.
let output_data_mem_offset = assert_matches!(
instance.invoke_export("rev_complement_output_ptr", &[test_data_ptr], &mut NopExternals),
Ok(Some(RuntimeValue::I32(v))) => v as u32,
"",
);
let result = memory
.get(output_data_mem_offset, REVCOMP_OUTPUT.len())
.expect("can't get result data from a wasm memory");
assert_eq!(&*result, REVCOMP_OUTPUT);
}
#[bench]
fn bench_regex_redux(b: &mut Bencher) {
let wasm_kernel = load_kernel();
let instance = ModuleInstance::new(&wasm_kernel, &ImportsBuilder::default())
.expect("failed to instantiate wasm module")
.assert_no_start();
// Allocate buffers for the input and output.
let test_data_ptr: RuntimeValue = {
let input_size = RuntimeValue::I32(REVCOMP_INPUT.len() as i32);
assert_matches!(
instance.invoke_export("prepare_regex_redux", &[input_size], &mut NopExternals),
Ok(Some(v @ RuntimeValue::I32(_))) => v,
"",
)
};
// Get the pointer to the input buffer.
let input_data_mem_offset = assert_matches!(
instance.invoke_export("regex_redux_input_ptr", &[test_data_ptr], &mut NopExternals),
Ok(Some(RuntimeValue::I32(v))) => v as u32,
"",
);
// Copy test data inside the wasm memory.
let memory = instance
.export_by_name("memory")
.expect("Expected export with a name 'memory'")
.as_memory()
.expect("'memory' should be a memory instance")
.clone();
memory
.set(input_data_mem_offset, REVCOMP_INPUT)
.expect("can't load test data into a wasm memory");
let func = instance.export_by_name("bench_regex_redux").unwrap();
let func = func.as_func().unwrap();
let mut interpreter = new_interpreter();
b.iter(|| {
interpreter.reset();
FuncInstance::invoke_configurable(&func, &[test_data_ptr], &mut NopExternals, &mut interpreter).unwrap()
});
}
#[bench]
fn fac_recursive(b: &mut Bencher) {
let wasm = wabt::wat2wasm(
r#"
;; Recursive factorial
(func (export "fac-rec") (param i64) (result i64)
(if (result i64) (i64.eq (get_local 0) (i64.const 0))
(then (i64.const 1))
(else
(i64.mul (get_local 0) (call 0 (i64.sub (get_local 0) (i64.const 1))))
)
)
)
"#,
).unwrap();
let module = Module::from_buffer(&wasm).unwrap();
let instance = ModuleInstance::new(&module, &ImportsBuilder::default())
.expect("failed to instantiate wasm module")
.assert_no_start();
let func = instance.export_by_name("fac-rec").unwrap();
let func = func.as_func().unwrap();
let mut interpreter = new_interpreter();
b.iter(|| {
interpreter.reset();
let value =
FuncInstance::invoke_configurable(&func, &[RuntimeValue::I64(25)], &mut NopExternals, &mut interpreter)
.unwrap();
assert_matches!(value, Some(RuntimeValue::I64(7034535277573963776)));
});
}
#[bench]
fn fac_opt(b: &mut Bencher) {
let wasm = wabt::wat2wasm(
r#"
;; Optimized factorial.
(func (export "fac-opt") (param i64) (result i64)
(local i64)
(set_local 1 (i64.const 1))
(block
(br_if 0 (i64.lt_s (get_local 0) (i64.const 2)))
(loop
(set_local 1 (i64.mul (get_local 1) (get_local 0)))
(set_local 0 (i64.add (get_local 0) (i64.const -1)))
(br_if 0 (i64.gt_s (get_local 0) (i64.const 1)))
)
)
(get_local 1)
)
"#,
).unwrap();
let module = Module::from_buffer(&wasm).unwrap();
let instance = ModuleInstance::new(&module, &ImportsBuilder::default())
.expect("failed to instantiate wasm module")
.assert_no_start();
let func = instance.export_by_name("fac-opt").unwrap();
let func = func.as_func().unwrap();
let mut interpreter = new_interpreter();
b.iter(|| {
interpreter.reset();
let value =
FuncInstance::invoke_configurable(&func, &[RuntimeValue::I64(25)], &mut NopExternals, &mut interpreter)
.unwrap();
assert_matches!(value, Some(RuntimeValue::I64(7034535277573963776)));
});
}
// This is used for testing overhead of a function call
// is not too large.
#[bench]
fn recursive_ok(b: &mut Bencher) {
let wasm = wabt::wat2wasm(
r#"
(module
(func $call (export "call") (param i32) (result i32)
block (result i32)
get_local 0
get_local 0
i32.eqz
br_if 0
i32.const 1
i32.sub
call $call
end
)
)
"#,
).unwrap();
let module = Module::from_buffer(&wasm).unwrap();
let instance = ModuleInstance::new(&module, &ImportsBuilder::default())
.expect("failed to instantiate wasm module")
.assert_no_start();
let func = instance.export_by_name("call").unwrap();
let func = func.as_func().unwrap();
let mut interpreter = new_interpreter();
b.iter(|| {
interpreter.reset();
let value =
FuncInstance::invoke_configurable(&func, &[RuntimeValue::I32(8000)], &mut NopExternals, &mut interpreter)
.unwrap();
assert_matches!(value, Some(RuntimeValue::I32(0)));
});
}
#[bench]
fn recursive_trap(b: &mut Bencher) {
let wasm = wabt::wat2wasm(
r#"
(module
(func $call (export "call") (param i32) (result i32)
block (result i32)
get_local 0
get_local 0
i32.eqz
br_if 0
i32.const 1
i32.sub
call $call
end
unreachable
)
)
"#,
).unwrap();
let module = Module::from_buffer(&wasm).unwrap();
let instance = ModuleInstance::new(&module, &ImportsBuilder::default())
.expect("failed to instantiate wasm module")
.assert_no_start();
let func = instance.export_by_name("call").unwrap();
let func = func.as_func().unwrap();
let mut interpreter = new_interpreter();
b.iter(|| {
interpreter.reset();
FuncInstance::invoke_configurable(&func, &[RuntimeValue::I32(1000)], &mut NopExternals, &mut interpreter)
.unwrap_err();
});
}