diff --git a/Cargo.lock b/Cargo.lock index 9b6b23ad04e..1de2fd1a546 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -886,6 +886,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + [[package]] name = "ansi_term" version = "0.12.1" @@ -2878,6 +2884,12 @@ dependencies = [ "toml 0.8.23", ] +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cbor4ii" version = "0.3.3" @@ -2989,6 +3001,33 @@ dependencies = [ "windows-link", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "cid" version = "0.9.0" @@ -3658,6 +3697,39 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "criterion" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1c047a62b0cc3e145fa84415a3191f628e980b194c2755aa12300a4e6cbd928" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap 4.5.54", + "criterion-plot", + "itertools 0.13.0", + "num-traits", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b1bcc0dc7dfae599d84ad0b1a55f80cde8af3725da8313b528da95ef783e338" +dependencies = [ + "cast", + "itertools 0.13.0", +] + [[package]] name = "critical-section" version = "1.2.0" @@ -7433,6 +7505,7 @@ name = "gear-sandbox-host" version = "1.10.0" dependencies = [ "atomic_enum", + "criterion", "defer", "environmental", "gear-sandbox-env", @@ -7814,6 +7887,7 @@ dependencies = [ "indexmap 2.14.0", "ipnet", "itertools 0.10.5", + "itertools 0.13.0", "itertools 0.14.0", "js-sys", "jsonrpsee", @@ -8039,6 +8113,7 @@ dependencies = [ "wasmtime-internal-cranelift", "winnow", "x25519-dalek", + "zerocopy", "zeroize", ] @@ -8507,6 +8582,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + [[package]] name = "handlebars" version = "5.1.2" @@ -12246,6 +12332,12 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + [[package]] name = "opaque-debug" version = "0.2.3" @@ -13806,6 +13898,34 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + [[package]] name = "polkavm" version = "0.9.3" @@ -19281,6 +19401,16 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "tinyvec" version = "1.10.0" diff --git a/Cargo.toml b/Cargo.toml index 74e3f3ab771..460adefe8f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -180,6 +180,7 @@ primitive-types = { version = "0.12.2", default-features = false } proc-macro2 = { version = "1", default-features = false } prometheus = { version = "0.14.0", default-features = false } proptest = "1.5.0" +criterion = { version = "0.7", features = ["html_reports"] } quick-xml = "0.28" quote = { version = "1.0.36", default-features = false } rand = { version = "0.8", default-features = false } diff --git a/protocol/sandbox/host/Cargo.toml b/protocol/sandbox/host/Cargo.toml index 120ca9b6be2..b47cd676946 100644 --- a/protocol/sandbox/host/Cargo.toml +++ b/protocol/sandbox/host/Cargo.toml @@ -30,6 +30,13 @@ region.workspace = true gear-workspace-hack.workspace = true +[dev-dependencies] +criterion.workspace = true + [features] # See wasmi/extra-checks for more information. wasmi-extra-checks = ["wasmi/extra-checks"] + +[[bench]] +name = "wasmtime_store_growth" +harness = false diff --git a/protocol/sandbox/host/benches/wasmtime_store_growth.rs b/protocol/sandbox/host/benches/wasmtime_store_growth.rs new file mode 100644 index 00000000000..399a163e9c2 --- /dev/null +++ b/protocol/sandbox/host/benches/wasmtime_store_growth.rs @@ -0,0 +1,157 @@ +// Copyright (C) Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +use criterion::{Criterion, criterion_group, criterion_main}; +use gear_sandbox_env::{EnvironmentDefinition, Instantiate}; +use gear_sandbox_host::{ + context::{HostResult, SupervisorContext, SupervisorContextDispatcher, SupervisorFuncIndex}, + sandbox::{GuestEnvironment, SandboxBackend, SandboxComponents}, +}; +use parity_scale_codec::Encode; +use sp_wasm_interface_common::{Pointer, WordSize}; +use std::{ + hint::black_box, + time::{Duration, Instant}, +}; + +const EMPTY_WASM: &[u8] = b"\0asm\x01\0\0\0"; +const MAX_OPERATIONS_PER_STORE: u64 = + (wasmtime::DEFAULT_INSTANCE_LIMIT as u64 + wasmtime::DEFAULT_MEMORY_LIMIT as u64) / 2; + +#[derive(Clone, Copy)] +enum ResetPolicy { + Never, + Every(usize), +} + +impl ResetPolicy { + fn as_str(self) -> &'static str { + match self { + Self::Never => "long_lived_store", + Self::Every(_) => "clear_periodically", + } + } +} + +struct NoopSupervisor; + +impl SupervisorContext for NoopSupervisor { + fn data_ptr(&self) -> *const () { + self as *const _ as *const () + } + + fn read_memory_into( + &self, + _address: Pointer, + dest: &mut [u8], + ) -> std::result::Result<(), String> { + dest.fill(0); + Ok(()) + } + + fn write_memory( + &mut self, + _address: Pointer, + _data: &[u8], + ) -> std::result::Result<(), String> { + Ok(()) + } + + fn allocate_memory(&mut self, _size: WordSize) -> std::result::Result, String> { + Ok(Pointer::null()) + } + + fn deallocate_memory(&mut self, _ptr: Pointer) -> std::result::Result<(), String> { + Ok(()) + } +} + +impl SupervisorContextDispatcher for NoopSupervisor { + fn dispatch_thunk_id(&self) -> u32 { + 0 + } + + fn invoke( + &mut self, + _invoke_args_ptr: Pointer, + _invoke_args_len: WordSize, + _func_idx: SupervisorFuncIndex, + ) -> HostResult { + Ok(0) + } +} + +fn bench_wasmtime_store_growth(c: &mut Criterion) { + let mut group = c.benchmark_group("wasmtime_store_growth"); + + for reset_policy in [ResetPolicy::Never, ResetPolicy::Every(50)] { + group.bench_function(reset_policy.as_str(), |b| { + b.iter_custom(|iters| { + let mut state = BenchState::new(); + let mut elapsed = Duration::ZERO; + for i in 0..iters { + if i.is_multiple_of(MAX_OPERATIONS_PER_STORE) { + state = BenchState::new(); + } + + let instant = Instant::now(); + state.run_iteration(i, reset_policy); + elapsed += instant.elapsed(); + } + elapsed + }) + }); + } + + group.finish(); +} + +struct BenchState { + store: SandboxComponents, + env_def: Vec, + supervisor: NoopSupervisor, +} + +impl BenchState { + fn new() -> Self { + Self { + store: SandboxComponents::new(SandboxBackend::Wasmtime), + env_def: EnvironmentDefinition { + entries: Vec::new(), + } + .encode(), + supervisor: NoopSupervisor, + } + } + + fn run_iteration(&mut self, iteration: u64, reset_policy: ResetPolicy) { + let memory_idx = self + .store + .new_memory(1, 1) + .expect("failed to create sandbox memory"); + black_box(memory_idx); + + let guest_env = GuestEnvironment::decode(&self.store, &self.env_def) + .unwrap_or_else(|_| panic!("failed to decode env")); + let instance = self + .store + .instantiate( + Instantiate::Version1, + EMPTY_WASM, + guest_env, + &mut self.supervisor, + ) + .unwrap_or_else(|_| panic!("failed to instantiate empty wasm")); + let instance_idx = instance.register(&mut self.store, self.supervisor.dispatch_thunk_id()); + black_box(instance_idx); + + if let ResetPolicy::Every(clear_every) = reset_policy + && iteration.is_multiple_of(clear_every as u64) + { + self.store.clear(); + } + } +} + +criterion_group!(benches, bench_wasmtime_store_growth); +criterion_main!(benches); diff --git a/protocol/sandbox/host/src/context.rs b/protocol/sandbox/host/src/context.rs index 72a990e9dc4..2d08f5a75e9 100644 --- a/protocol/sandbox/host/src/context.rs +++ b/protocol/sandbox/host/src/context.rs @@ -3,10 +3,7 @@ #![allow(missing_docs)] -use core::{ - cell::RefCell, - sync::atomic::{AtomicU32, Ordering}, -}; +use core::{cell::RefCell, sync::atomic::Ordering}; use parity_scale_codec::{Decode, Encode}; use std::{ panic::{self, AssertUnwindSafe}, @@ -24,17 +21,8 @@ pub use sp_wasm_interface_common::{HostPointer, Pointer, ReturnValue, Value, Wor static SANDBOX_BACKEND_TYPE: sandbox_env::AtomicSandboxBackend = sandbox_env::AtomicSandboxBackend::new(sandbox_env::SandboxBackend::Wasmtime); -const DEFAULT_SANDBOX_STORE_CLEAR_COUNTER_LIMIT: u32 = 50; - -static SANDBOX_STORE_CLEAR_COUNTER_LIMIT: AtomicU32 = - AtomicU32::new(DEFAULT_SANDBOX_STORE_CLEAR_COUNTER_LIMIT); - -pub fn init(sandbox_backend: sandbox_env::SandboxBackend, store_clear_counter_limit: Option) { +pub fn init(sandbox_backend: sandbox_env::SandboxBackend) { SANDBOX_BACKEND_TYPE.store(sandbox_backend, Ordering::SeqCst); - SANDBOX_STORE_CLEAR_COUNTER_LIMIT.store( - store_clear_counter_limit.unwrap_or(DEFAULT_SANDBOX_STORE_CLEAR_COUNTER_LIMIT), - Ordering::SeqCst, - ); } pub struct Sandboxes { @@ -60,14 +48,6 @@ impl Sandboxes { &mut self.store } - - pub fn clear(&mut self, counter: &mut u32) { - if *counter >= SANDBOX_STORE_CLEAR_COUNTER_LIMIT.load(Ordering::SeqCst) { - *counter = 0; - self.store.clear(); - } - *counter += 1; - } } impl Default for Sandboxes { @@ -79,7 +59,6 @@ impl Default for Sandboxes { #[derive(Default)] struct ThreadState { sandboxes: Sandboxes, - clear_counter: u32, } thread_local! { @@ -333,7 +312,6 @@ pub fn memory_grow(supervisor_context: impl SupervisorContext, memory_idx: u32, pub fn memory_new(supervisor_context: impl SupervisorContext, initial: u32, maximum: u32) -> u32 { with_thread_state(|state| { - state.sandboxes.clear(&mut state.clear_counter); state .sandboxes .get(supervisor_context.data_ptr()) diff --git a/utils/gear-workspace-hack/Cargo.toml b/utils/gear-workspace-hack/Cargo.toml index 6d3d84d25d4..26fb6970099 100644 --- a/utils/gear-workspace-hack/Cargo.toml +++ b/utils/gear-workspace-hack/Cargo.toml @@ -262,6 +262,7 @@ const-hex = { version = "1", features = ["core-error", "serde"] } cranelift-bitset = { version = "0.131", default-features = false, features = ["enable-serde"] } crc32fast = { version = "1" } crossbeam-channel = { version = "0.5" } +crossbeam-epoch = { version = "0.9" } crossbeam-utils = { version = "0.8" } crunchy = { version = "0.2", features = ["std"] } crypto-common = { version = "0.1", default-features = false, features = ["getrandom", "std"] } @@ -322,6 +323,7 @@ impl-serde = { version = "0.4" } indexmap = { version = "2", features = ["serde"] } ipnet = { version = "2" } itertools-582f2526e08bb6a0 = { package = "itertools", version = "0.14" } +itertools-594e8ee84c453af0 = { package = "itertools", version = "0.13" } itertools-93f6ce9d446188ac = { package = "itertools", version = "0.10" } js-sys = { version = "0.3" } jsonrpsee = { version = "0.24", default-features = false, features = ["client", "macros", "server"] } @@ -497,6 +499,7 @@ wasmtime-internal-core = { version = "44", default-features = false, features = wasmtime-internal-cranelift = { version = "44", default-features = false, features = ["component-model", "gc-drc", "gc-null", "pulley", "stack-switching", "threads"] } winnow = { version = "0.7" } x25519-dalek = { version = "2", features = ["static_secrets"] } +zerocopy = { version = "0.8", default-features = false, features = ["derive", "simd"] } zeroize = { version = "1", features = ["derive", "std"] } [target.'cfg(not(target_arch = "wasm32"))'.build-dependencies] @@ -551,6 +554,7 @@ const-hex = { version = "1", features = ["core-error", "serde"] } cranelift-bitset = { version = "0.131", default-features = false, features = ["enable-serde"] } crc32fast = { version = "1" } crossbeam-channel = { version = "0.5" } +crossbeam-epoch = { version = "0.9" } crossbeam-utils = { version = "0.8" } crunchy = { version = "0.2", features = ["std"] } crypto-common = { version = "0.1", default-features = false, features = ["getrandom", "std"] } @@ -613,6 +617,7 @@ impl-serde = { version = "0.4" } indexmap = { version = "2", features = ["serde"] } ipnet = { version = "2" } itertools-582f2526e08bb6a0 = { package = "itertools", version = "0.14" } +itertools-594e8ee84c453af0 = { package = "itertools", version = "0.13" } itertools-93f6ce9d446188ac = { package = "itertools", version = "0.10" } js-sys = { version = "0.3" } jsonrpsee = { version = "0.24", default-features = false, features = ["client", "macros", "server"] } @@ -793,10 +798,10 @@ wasmtime-internal-core = { version = "44", default-features = false, features = wasmtime-internal-cranelift = { version = "44", default-features = false, features = ["component-model", "gc-drc", "gc-null", "pulley", "stack-switching", "threads"] } winnow = { version = "0.7" } x25519-dalek = { version = "2", features = ["static_secrets"] } +zerocopy = { version = "0.8", default-features = false, features = ["derive", "simd"] } zeroize = { version = "1", features = ["derive", "std"] } [target.x86_64-unknown-linux-gnu.dependencies] -crossbeam-epoch = { version = "0.9" } errno = { version = "0.3" } hickory-proto = { version = "0.25", default-features = false, features = ["mdns", "tokio"] } hyper-rustls = { version = "0.27", default-features = false, features = ["aws-lc-rs", "http1", "http2", "logging", "ring", "tls12", "webpki-tokio"] } @@ -819,7 +824,6 @@ tokio-rustls = { version = "0.26", default-features = false, features = ["aws-lc unicode-normalization = { version = "0.1" } [target.x86_64-unknown-linux-gnu.build-dependencies] -crossbeam-epoch = { version = "0.9" } errno = { version = "0.3" } hickory-proto = { version = "0.25", default-features = false, features = ["mdns", "tokio"] } hyper-rustls = { version = "0.27", default-features = false, features = ["aws-lc-rs", "http1", "http2", "logging", "ring", "tls12", "webpki-tokio"] } @@ -842,7 +846,6 @@ tokio-rustls = { version = "0.26", default-features = false, features = ["aws-lc unicode-normalization = { version = "0.1" } [target.aarch64-unknown-linux-gnu.dependencies] -crossbeam-epoch = { version = "0.9" } errno = { version = "0.3" } hickory-proto = { version = "0.25", default-features = false, features = ["mdns", "tokio"] } hyper-rustls = { version = "0.27", default-features = false, features = ["aws-lc-rs", "http1", "http2", "logging", "ring", "tls12", "webpki-tokio"] } @@ -865,7 +868,6 @@ tokio-rustls = { version = "0.26", default-features = false, features = ["aws-lc unicode-normalization = { version = "0.1" } [target.aarch64-unknown-linux-gnu.build-dependencies] -crossbeam-epoch = { version = "0.9" } errno = { version = "0.3" } hickory-proto = { version = "0.25", default-features = false, features = ["mdns", "tokio"] } hyper-rustls = { version = "0.27", default-features = false, features = ["aws-lc-rs", "http1", "http2", "logging", "ring", "tls12", "webpki-tokio"] } @@ -888,7 +890,6 @@ tokio-rustls = { version = "0.26", default-features = false, features = ["aws-lc unicode-normalization = { version = "0.1" } [target.aarch64-apple-darwin.dependencies] -crossbeam-epoch = { version = "0.9" } errno = { version = "0.3" } hickory-proto = { version = "0.25", default-features = false, features = ["mdns", "tokio"] } hyper-rustls = { version = "0.27", default-features = false, features = ["aws-lc-rs", "http1", "http2", "logging", "ring", "tls12", "webpki-tokio"] } @@ -909,7 +910,6 @@ tokio-rustls = { version = "0.26", default-features = false, features = ["aws-lc unicode-normalization = { version = "0.1" } [target.aarch64-apple-darwin.build-dependencies] -crossbeam-epoch = { version = "0.9" } errno = { version = "0.3" } hickory-proto = { version = "0.25", default-features = false, features = ["mdns", "tokio"] } hyper-rustls = { version = "0.27", default-features = false, features = ["aws-lc-rs", "http1", "http2", "logging", "ring", "tls12", "webpki-tokio"] } diff --git a/vara/node/cli/src/cli.rs b/vara/node/cli/src/cli.rs index 5d1de448fb5..e1afa31f261 100644 --- a/vara/node/cli/src/cli.rs +++ b/vara/node/cli/src/cli.rs @@ -38,12 +38,6 @@ pub struct RunCmd { #[arg(long, default_value_t = SandboxBackend::Wasmtime)] pub sandbox_backend: SandboxBackend, - /// Sets a limit at which the underlying sandbox store will be cleared (applies only to the Wasmtime sandbox backend), - /// potentially altering performance characteristics. - // TODO: remove clear counter - #[arg(long, default_value_t = 50)] - pub sandbox_store_clear_counter_limit: u32, - /// The upper limit for the amount of gas a validator can burn in one block. #[arg(long)] pub max_gas: Option, diff --git a/vara/node/cli/src/command.rs b/vara/node/cli/src/command.rs index 5c8a351c5e7..6a3d47a7ccf 100644 --- a/vara/node/cli/src/command.rs +++ b/vara/node/cli/src/command.rs @@ -119,13 +119,10 @@ macro_rules! unwrap_client { pub fn run() -> sc_cli::Result<()> { let cli = Cli::from_args(); - gear_runtime_interface::sandbox_init( - match cli.run.sandbox_backend { - SandboxBackend::Wasmtime => gear_runtime_interface::SandboxBackend::Wasmtime, - SandboxBackend::Wasmi => gear_runtime_interface::SandboxBackend::Wasmi, - }, - cli.run.sandbox_store_clear_counter_limit.into(), - ); + gear_runtime_interface::sandbox_init(match cli.run.sandbox_backend { + SandboxBackend::Wasmtime => gear_runtime_interface::SandboxBackend::Wasmtime, + SandboxBackend::Wasmi => gear_runtime_interface::SandboxBackend::Wasmi, + }); let old_base = BasePath::from_project("", "", "gear-node"); let new_base = BasePath::from_project("", "", &Cli::executable_name()); diff --git a/vara/runtime/interface/sandbox/src/detail.rs b/vara/runtime/interface/sandbox/src/detail.rs index 5c09a90c15e..9ff184f0f0a 100644 --- a/vara/runtime/interface/sandbox/src/detail.rs +++ b/vara/runtime/interface/sandbox/src/detail.rs @@ -9,11 +9,8 @@ use sp_wasm_interface::{ wasmtime::{AsContext, AsContextMut, Val}, }; -pub fn init( - sandbox_backend: gear_sandbox_host::sandbox::SandboxBackend, - store_clear_counter_limit: Option, -) { - context::init(sandbox_backend, store_clear_counter_limit); +pub fn init(sandbox_backend: gear_sandbox_host::sandbox::SandboxBackend) { + context::init(sandbox_backend); } struct RuntimeInterfaceContext<'a, 'b> { diff --git a/vara/tools/gear-replay-cli/src/cmd/mod.rs b/vara/tools/gear-replay-cli/src/cmd/mod.rs index f37f0818941..596cfa0bb5e 100644 --- a/vara/tools/gear-replay-cli/src/cmd/mod.rs +++ b/vara/tools/gear-replay-cli/src/cmd/mod.rs @@ -51,10 +51,7 @@ pub enum Command { impl Command { pub async fn run(&self, shared: &SharedParams) -> sc_cli::Result<()> { - gear_runtime_interface::sandbox_init( - gear_runtime_interface::SandboxBackend::Wasmtime, - None, - ); + gear_runtime_interface::sandbox_init(gear_runtime_interface::SandboxBackend::Wasmtime); match &self { Command::ReplayBlock(cmd) => {