Skip to content
This repository was archived by the owner on Oct 3, 2025. It is now read-only.

Commit 868aa00

Browse files
chore: slight perf improvements
Signed-off-by: Henry Gressmann <mail@henrygressmann.de>
1 parent 2ac04cd commit 868aa00

File tree

24 files changed

+200
-314
lines changed

24 files changed

+200
-314
lines changed

BENCHMARKS.md

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,22 +26,18 @@ All runtimes are compiled with the following settings:
2626

2727
## Versions
2828

29-
- `tinywasm`: `0.4.1`
29+
- `tinywasm`: `0.6.2`
3030
- `wasmi`: `0.31.2`
31-
- `wasmer`: `4.2.5`
31+
- `wasmer`: `4.2.8`
3232

3333
## Results
3434

35-
| Benchmark | Native | TinyWasm\* | Wasmi | Wasmer (Single Pass) |
35+
| Benchmark | Native | TinyWasm | Wasmi | Wasmer (Single Pass) |
3636
| ------------ | -------- | ---------- | --------- | -------------------- |
37-
| `fib` | \*\* | ` 43.60µs` | `48.27µs` | ` 44.99µs` |
38-
| `fib-rec` | `0.27ms` | ` 21.13ms` | ` 4.63ms` | ` 0.47ms` |
39-
| `argon2id` | `0.53ms` | ` 86.16ms` | `45.00ms` | ` 4.59ms` |
40-
| `selfhosted` | `0.05ms` | ` 1.84ms` | ` 6.51ms` | `446.48ms` |
41-
42-
_\* Uses tinywasm's internal module format instead of `wasm`. It takes ~5.7ms to parse and validate `tinywasm.wasm`._
43-
44-
_\*\* essentially instant as it gets computed at compile time._
37+
| `fib` | `0ms` | ` 19.09µs` | `18.53µs` | ` 48.09µs` |
38+
| `fib-rec` | `0.27ms` | ` 22.22ms` | ` 4.96ms` | ` 0.47ms` |
39+
| `argon2id` | `0.53ms` | ` 86.42ms` | `46.36ms` | ` 4.82ms` |
40+
| `selfhosted` | `0.05ms` | ` 7.26ms` | ` 6.51ms` | `446.48ms` |
4541

4642
### Fib
4743

benchmarks/benches/argon2id.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
mod util;
22
use criterion::{black_box, criterion_group, criterion_main, Criterion};
3-
use util::wasm_to_twasm;
43

5-
fn run_tinywasm(twasm: &[u8], params: (i32, i32, i32), name: &str) {
6-
let (mut store, instance) = util::tinywasm(twasm);
4+
fn run_tinywasm(wasm: &[u8], params: (i32, i32, i32), name: &str) {
5+
let (mut store, instance) = util::tinywasm(wasm);
76
let argon2 = instance.exported_func::<(i32, i32, i32), i32>(&store, name).expect("exported_func");
87
argon2.call(&mut store, params).expect("call");
98
}
@@ -38,15 +37,14 @@ fn run_native(params: (i32, i32, i32)) {
3837

3938
const ARGON2ID: &[u8] = include_bytes!("../../examples/rust/out/argon2id.wasm");
4039
fn criterion_benchmark(c: &mut Criterion) {
41-
let twasm = wasm_to_twasm(ARGON2ID);
4240
let params = (1000, 2, 1);
4341

4442
let mut group = c.benchmark_group("argon2id");
4543
group.measurement_time(std::time::Duration::from_secs(7));
4644
group.sample_size(10);
4745

4846
group.bench_function("native", |b| b.iter(|| run_native(black_box(params))));
49-
group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(&twasm, black_box(params), "argon2id")));
47+
group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(ARGON2ID, black_box(params), "argon2id")));
5048
group.bench_function("wasmi", |b| b.iter(|| run_wasmi(ARGON2ID, black_box(params), "argon2id")));
5149
group.bench_function("wasmer", |b| b.iter(|| run_wasmer(ARGON2ID, black_box(params), "argon2id")));
5250
}

benchmarks/benches/fibonacci.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
mod util;
22
use criterion::{black_box, criterion_group, criterion_main, Criterion};
3-
use util::wasm_to_twasm;
43

5-
fn run_tinywasm(twasm: &[u8], iterations: i32, name: &str) {
6-
let (mut store, instance) = util::tinywasm(twasm);
4+
fn run_tinywasm(wasm: &[u8], iterations: i32, name: &str) {
5+
let (mut store, instance) = util::tinywasm(wasm);
76
let fib = instance.exported_func::<i32, i32>(&store, name).expect("exported_func");
87
fib.call(&mut store, iterations).expect("call");
98
}
@@ -47,12 +46,10 @@ fn run_native_recursive(n: i32) -> i32 {
4746

4847
const FIBONACCI: &[u8] = include_bytes!("../../examples/rust/out/fibonacci.wasm");
4948
fn criterion_benchmark(c: &mut Criterion) {
50-
let twasm = wasm_to_twasm(FIBONACCI);
51-
5249
{
5350
let mut group = c.benchmark_group("fibonacci");
5451
group.bench_function("native", |b| b.iter(|| run_native(black_box(60))));
55-
group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(&twasm, black_box(60), "fibonacci")));
52+
group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(FIBONACCI, black_box(60), "fibonacci")));
5653
group.bench_function("wasmi", |b| b.iter(|| run_wasmi(FIBONACCI, black_box(60), "fibonacci")));
5754
group.bench_function("wasmer", |b| b.iter(|| run_wasmer(FIBONACCI, black_box(60), "fibonacci")));
5855
}
@@ -61,7 +58,7 @@ fn criterion_benchmark(c: &mut Criterion) {
6158
let mut group = c.benchmark_group("fibonacci-recursive");
6259
group.measurement_time(std::time::Duration::from_secs(5));
6360
group.bench_function("native", |b| b.iter(|| run_native_recursive(black_box(26))));
64-
group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(&twasm, black_box(26), "fibonacci_recursive")));
61+
group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(FIBONACCI, black_box(26), "fibonacci_recursive")));
6562
group.bench_function("wasmi", |b| b.iter(|| run_wasmi(FIBONACCI, black_box(26), "fibonacci_recursive")));
6663
group.bench_function("wasmer", |b| b.iter(|| run_wasmer(FIBONACCI, black_box(26), "fibonacci_recursive")));
6764
}

benchmarks/benches/selfhosted.rs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
mod util;
2-
use crate::util::twasm_to_module;
32
use criterion::{criterion_group, criterion_main, Criterion};
43

54
fn run_native() {
@@ -15,7 +14,7 @@ fn run_native() {
1514

1615
fn run_tinywasm(twasm: &[u8]) {
1716
use tinywasm::*;
18-
let module = twasm_to_module(twasm);
17+
let module = Module::parse_bytes(twasm).expect("Module::parse_bytes");
1918
let mut store = Store::default();
2019
let mut imports = Imports::default();
2120
imports.define("env", "printi32", Extern::typed_func(|_: FuncContext<'_>, _: i32| Ok(()))).expect("define");
@@ -54,15 +53,9 @@ fn run_wasmer(wasm: &[u8]) {
5453
const TINYWASM: &[u8] = include_bytes!("../../examples/rust/out/tinywasm.wasm");
5554
fn criterion_benchmark(c: &mut Criterion) {
5655
{
57-
let mut group = c.benchmark_group("selfhosted-parse");
58-
group.bench_function("tinywasm", |b| b.iter(|| util::parse_wasm(TINYWASM)));
59-
}
60-
61-
{
62-
let twasm = util::wasm_to_twasm(TINYWASM);
6356
let mut group = c.benchmark_group("selfhosted");
6457
group.bench_function("native", |b| b.iter(run_native));
65-
group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(&twasm)));
58+
group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(TINYWASM)));
6659
group.bench_function("wasmi", |b| b.iter(|| run_wasmi(TINYWASM)));
6760
group.bench_function("wasmer", |b| b.iter(|| run_wasmer(TINYWASM)));
6861
}

benchmarks/benches/util/mod.rs

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,8 @@
11
#![allow(dead_code)]
22

3-
use tinywasm::{parser::Parser, types::TinyWasmModule};
4-
5-
pub fn parse_wasm(wasm: &[u8]) -> TinyWasmModule {
6-
let parser = Parser::new();
7-
parser.parse_module_bytes(wasm).expect("parse_module_bytes")
8-
}
9-
10-
pub fn wasm_to_twasm(wasm: &[u8]) -> Vec<u8> {
11-
let parser = Parser::new();
12-
let res = parser.parse_module_bytes(wasm).expect("parse_module_bytes");
13-
res.serialize_twasm().to_vec()
14-
}
15-
16-
#[inline]
17-
pub fn twasm_to_module(twasm: &[u8]) -> tinywasm::Module {
18-
unsafe { TinyWasmModule::from_twasm_unchecked(twasm) }.into()
19-
}
20-
21-
pub fn tinywasm(twasm: &[u8]) -> (tinywasm::Store, tinywasm::ModuleInstance) {
3+
pub fn tinywasm(wasm: &[u8]) -> (tinywasm::Store, tinywasm::ModuleInstance) {
224
use tinywasm::*;
23-
let module = twasm_to_module(twasm);
5+
let module = Module::parse_bytes(wasm).expect("Module::parse_bytes");
246
let mut store = Store::default();
257
let imports = Imports::default();
268
let instance = ModuleInstance::instantiate(&mut store, module, Some(imports)).expect("instantiate");

crates/parser/src/visit.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder {
343343
self.instructions.pop();
344344
self.visit(Instruction::I32StoreLocal {
345345
local: a,
346-
consti32: b,
346+
const_i32: b,
347347
offset: arg.offset as u32,
348348
mem_addr: arg.mem_addr as u8,
349349
})

crates/tinywasm/src/func.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ impl FuncHandle {
4949
return Err(Error::Other("Type mismatch".into()));
5050
}
5151

52-
let func_inst = store.get_func(self.addr as usize)?;
52+
let func_inst = store.get_func(self.addr)?;
5353
let wasm_func = match &func_inst.func {
5454
Function::Host(host_func) => {
5555
let func = &host_func.clone().func;

crates/tinywasm/src/imports.rs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -226,11 +226,8 @@ pub struct Imports {
226226
}
227227

228228
pub(crate) enum ResolvedExtern<S, V> {
229-
// already in the store
230-
Store(S),
231-
232-
// needs to be added to the store, provided value
233-
Extern(V),
229+
Store(S), // already in the store
230+
Extern(V), // needs to be added to the store, provided value
234231
}
235232

236233
pub(crate) struct ResolvedImports {
@@ -391,17 +388,17 @@ impl Imports {
391388

392389
match (val, &import.kind) {
393390
(ExternVal::Global(global_addr), ImportKind::Global(ty)) => {
394-
let global = store.get_global(global_addr as usize)?;
391+
let global = store.get_global(global_addr)?;
395392
Self::compare_types(import, &global.borrow().ty, ty)?;
396393
imports.globals.push(global_addr);
397394
}
398395
(ExternVal::Table(table_addr), ImportKind::Table(ty)) => {
399-
let table = store.get_table(table_addr as usize)?;
396+
let table = store.get_table(table_addr)?;
400397
Self::compare_table_types(import, &table.borrow().kind, ty)?;
401398
imports.tables.push(table_addr);
402399
}
403400
(ExternVal::Memory(memory_addr), ImportKind::Memory(ty)) => {
404-
let mem = store.get_mem(memory_addr as usize)?;
401+
let mem = store.get_mem(memory_addr)?;
405402
let (size, kind) = {
406403
let mem = mem.borrow();
407404
(mem.page_count(), mem.kind)
@@ -410,7 +407,7 @@ impl Imports {
410407
imports.memories.push(memory_addr);
411408
}
412409
(ExternVal::Func(func_addr), ImportKind::Function(ty)) => {
413-
let func = store.get_func(func_addr as usize)?;
410+
let func = store.get_func(func_addr)?;
414411
let import_func_type = module
415412
.data
416413
.func_types

crates/tinywasm/src/instance.rs

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use alloc::{boxed::Box, format, rc::Rc, string::ToString};
22
use tinywasm_types::*;
33

44
use crate::func::{FromWasmValueTuple, IntoWasmValueTuple};
5-
use crate::{log, Error, FuncHandle, FuncHandleTyped, Imports, MemoryRef, MemoryRefMut, Module, Result, Store};
5+
use crate::{Error, FuncHandle, FuncHandleTyped, Imports, MemoryRef, MemoryRefMut, Module, Result, Store};
66

77
/// An instanciated WebAssembly module
88
///
@@ -61,13 +61,9 @@ impl ModuleInstance {
6161
// don't need to create a auxiliary frame etc.
6262

6363
let idx = store.next_module_instance_idx();
64-
log::info!("Instantiating module at index {}", idx);
65-
let imports = imports.unwrap_or_default();
66-
67-
let mut addrs = imports.link(store, &module, idx)?;
64+
let mut addrs = imports.unwrap_or_default().link(store, &module, idx)?;
6865
let data = module.data;
6966

70-
// TODO: check if the compiler correctly optimizes this to prevent wasted allocations
7167
addrs.funcs.extend(store.init_funcs(data.funcs.into(), idx)?);
7268
addrs.tables.extend(store.init_tables(data.table_types.into(), idx)?);
7369
addrs.memories.extend(store.init_memories(data.memory_types.into(), idx)?);
@@ -110,15 +106,14 @@ impl ModuleInstance {
110106
/// Get a export by name
111107
pub fn export_addr(&self, name: &str) -> Option<ExternVal> {
112108
let exports = self.0.exports.iter().find(|e| e.name == name.into())?;
113-
let kind = exports.kind.clone();
114-
let addr = match kind {
109+
let addr = match exports.kind {
115110
ExternalKind::Func => self.0.func_addrs.get(exports.index as usize)?,
116111
ExternalKind::Table => self.0.table_addrs.get(exports.index as usize)?,
117112
ExternalKind::Memory => self.0.mem_addrs.get(exports.index as usize)?,
118113
ExternalKind::Global => self.0.global_addrs.get(exports.index as usize)?,
119114
};
120115

121-
Some(ExternVal::new(kind, *addr))
116+
Some(ExternVal::new(exports.kind.clone(), *addr))
122117
}
123118

124119
#[inline]
@@ -183,7 +178,7 @@ impl ModuleInstance {
183178
return Err(Error::Other(format!("Export is not a function: {}", name)));
184179
};
185180

186-
let func_inst = store.get_func(func_addr as usize)?;
181+
let func_inst = store.get_func(func_addr)?;
187182
let ty = func_inst.func.ty();
188183

189184
Ok(FuncHandle { addr: func_addr, module_addr: self.id(), name: Some(name.to_string()), ty: ty.clone() })
@@ -205,8 +200,8 @@ impl ModuleInstance {
205200
let ExternVal::Memory(mem_addr) = export else {
206201
return Err(Error::Other(format!("Export is not a memory: {}", name)));
207202
};
208-
let mem = self.memory(store, mem_addr)?;
209-
Ok(mem)
203+
204+
self.memory(store, mem_addr)
210205
}
211206

212207
/// Get an exported memory by name
@@ -215,21 +210,19 @@ impl ModuleInstance {
215210
let ExternVal::Memory(mem_addr) = export else {
216211
return Err(Error::Other(format!("Export is not a memory: {}", name)));
217212
};
218-
let mem = self.memory_mut(store, mem_addr)?;
219-
Ok(mem)
213+
214+
self.memory_mut(store, mem_addr)
220215
}
221216

222217
/// Get a memory by address
223218
pub fn memory<'a>(&self, store: &'a mut Store, addr: MemAddr) -> Result<MemoryRef<'a>> {
224-
let addr = self.resolve_mem_addr(addr);
225-
let mem = store.get_mem(addr as usize)?;
219+
let mem = store.get_mem(self.resolve_mem_addr(addr))?;
226220
Ok(MemoryRef { instance: mem.borrow() })
227221
}
228222

229223
/// Get a memory by address (mutable)
230224
pub fn memory_mut<'a>(&self, store: &'a mut Store, addr: MemAddr) -> Result<MemoryRefMut<'a>> {
231-
let addr = self.resolve_mem_addr(addr);
232-
let mem = store.get_mem(addr as usize)?;
225+
let mem = store.get_mem(self.resolve_mem_addr(addr))?;
233226
Ok(MemoryRefMut { instance: mem.borrow_mut() })
234227
}
235228

@@ -257,7 +250,7 @@ impl ModuleInstance {
257250
};
258251

259252
let func_addr = self.0.func_addrs.get(func_index as usize).expect("No func addr for start func, this is a bug");
260-
let func_inst = store.get_func(*func_addr as usize)?;
253+
let func_inst = store.get_func(*func_addr)?;
261254
let ty = func_inst.func.ty();
262255

263256
Ok(Some(FuncHandle { module_addr: self.id(), addr: *func_addr, ty: ty.clone(), name: None }))

crates/tinywasm/src/lib.rs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -93,15 +93,13 @@ pub(crate) mod log {
9393
}
9494

9595
mod error;
96-
pub use {
97-
error::*,
98-
func::{FuncHandle, FuncHandleTyped},
99-
imports::*,
100-
instance::ModuleInstance,
101-
module::Module,
102-
reference::*,
103-
store::*,
104-
};
96+
pub use error::*;
97+
pub use func::{FuncHandle, FuncHandleTyped};
98+
pub use imports::*;
99+
pub use instance::ModuleInstance;
100+
pub use module::Module;
101+
pub use reference::*;
102+
pub use store::*;
105103

106104
mod func;
107105
mod imports;

0 commit comments

Comments
 (0)