koute opened PR #3691 from main_cow_instance_reuse to main:
This PR adds a new copy-on-write based instance reuse mechanism on Linux.
Usage
The general idea is - you instantiate your instance once, and then you can reset its state back to how it was when it was initially instantiated.
// Initialize a new instance. This is done only once. let mut store = Store::new(&engine, ()); let instance_pre = linker.instantiate_pre(&mut store, &module)?; let instance = instance_pre.instantiate_reusable(&mut store)?; loop { // Call methods, modify memory, modify the table, etc. use_instance(instance, &mut store)?; // Reset the instance to its initial state. instance.reset(&mut store)?; }How does it work?
After the instance is instantiated a snapshot of its state is taken. Tables and globals are simply cloned into a spare
Vec, while the memory is copied into anmemfdand remapped inplace in a copy-on-write fashion. Then whenresetis called the tables and the globals are restored by simply copying them back over, and the memory is reset usingmadvise(MADV_DONTNEED)which either clears the memory (for those pages which are not mapped to thememfd) or restores the original contents (for those pages which are mapped to thememfd).Benchmarks
In our benchmarks this is currently the fastest way to call into a WASM module assuming you rarely need to instantiate it from scratch.


Legend:
-instance_pooling_with_uffd: create a fresh instance withInstanceAllocationStrategy::Poolingstrategy withuffdturned on
-instance_pooling_without_uffd: create a fresh instance withInstanceAllocationStrategy::Poolingstrategy withoutuffdturned on
-recreate_instance: create a fresh instance withInstanceAllocationStrategy::OnDemandstrategy
-native_instance_reuse: this PR
-interpreted: just for our own reference; an instance created through thewasmicrate
-legacy_instance_reuse: just for our own reference; this is what we're currently using - it is an instance spawned withInstanceAllocationStrategy::OnDemandstrategy and then reused after manually clearing its memory and restoring its data segments and globalsThe two of the benchmarks shown here are:
-call_empty_function: an empty function is called in a loop, resetting (or recreating) the instance after each call
-dirty_1mb_of_memory: a function which dirties 1MB of memory and then returns is called in a loop, resetting (or recreating) the instance after each callThe measurements are only for the main thread; thread count on the bottom signifies how many other threads were running in the background doing exactly the same thing as the main thread, e.g. for 4 threads there was 1 thread (the main thread) being benchmarked while other 3 threads were running in the background.
For your reference the benchmarks used to generate these graphs can be found here:
https://github.com/koute/substrate/tree/master_wasmtime_benchmarksWhich can be run like this after cloning the repository:
$ cd substrate/client/executor/benches # `instance_pooling_with_uffd` $ FORCE_WASMTIME_INSTANCE_POOLING=1 rustup run nightly-2021-11-01-x86_64-unknown-linux-gnu cargo bench --features wasmtime,sc-executor-wasmtime/uffd --bench bench -- with_recreate_instance # `instance_pooling_without_uffd` $ FORCE_WASMTIME_INSTANCE_POOLING=1 rustup run nightly-2021-11-01-x86_64-unknown-linux-gnu cargo bench --features wasmtime --bench bench -- with_recreate_instance # `recreate_instance` FORCE_WASMTIME_INSTANCE_POOLING=0 rustup run nightly-2021-11-01-x86_64-unknown-linux-gnu cargo bench --features wasmtime --bench bench -- with_recreate_instance # `native_instance_reuse` rustup run nightly-2021-11-01-x86_64-unknown-linux-gnu cargo bench --features wasmtime --bench bench -- native_instance_reuse(The rustc version is just for reference as to what I used. Also please forgive the hacky way the benchmarks have to be launched for instance pooling; we don't intend to keep this codepath, so I quckly hacked it in only for the benchmarks.)
The benchmarks were run on the following machine:
AMD Threadripper 3970x (32-core)
64GB of RAM
Linux 5.14.16Possible future work (missing features)
- Add support for other OSes (this is Linux-only for now)
- Add support for imported memories (this only supports modules which do not import memory)
- Add support for taking snapshots at an arbitrary points in time (it always takes a snapshot right after instantiation)
- Add the ability to customize what is restored on reset (it always resets everything)
- Add address-space pooling to make the initial initialization faster (for cases where new modules are frequently instantiated from scratch)
koute updated PR #3691 from main_cow_instance_reuse to main.
koute submitted PR review.
koute created PR review comment:
I'm pretty sure this is necessary for correctness, but when I comment it out nothing fails. Any idea how I could write a test which would verify that this is here?
bjorn3 submitted PR review.
bjorn3 created PR review comment:
This doesn't work if
/procisn't mounted. In addition it doesn't check/procis actually a mounted procfs. Rustix has code to check if the/procis sane. It doesn't seem like it is exported though.
programmerjake created PR review comment:
why not just pass by value?
programmerjake submitted PR review.
koute updated PR #3691 from main_cow_instance_reuse to main.
koute submitted PR review.
koute created PR review comment:
A remnant from before when I had a different implementation where
MemorySourcewas heavier so it wasn'tCopy.Fixed!
koute updated PR #3691 from main_cow_instance_reuse to main.
koute submitted PR review.
koute created PR review comment:
Good point.
I've made a PR to
rustixhere to export it: https://github.com/bytecodealliance/rustix/pull/174
koute updated PR #3691 from main_cow_instance_reuse to main.
koute updated PR #3691 from main_cow_instance_reuse to main.
koute updated PR #3691 from main_cow_instance_reuse to main.
koute updated PR #3691 from main_cow_instance_reuse to main.
koute closed without merge PR #3691.
Last updated: Dec 06 2025 at 06:05 UTC