From 87d1939ab4b3e01efc5bae6b083609692d70be42 Mon Sep 17 00:00:00 2001 From: anonymousGiga Date: Sat, 9 Dec 2023 14:29:16 +0800 Subject: [PATCH] Perf: Add metrics to facilitate performance testing. --- Cargo.lock | 35 ++++ Cargo.toml | 3 + crates/interpreter/Cargo.toml | 3 + .../src/instructions/arithmetic.rs | 5 +- crates/interpreter/src/instructions/host.rs | 170 ++++++++++++------ .../src/instructions/host/call_helpers.rs | 3 + crates/interpreter/src/instructions/memory.rs | 5 +- crates/interpreter/src/instructions/system.rs | 20 ++- crates/interpreter/src/interpreter.rs | 6 +- crates/primitives/Cargo.toml | 2 + crates/primitives/src/state.rs | 14 ++ crates/revm/Cargo.toml | 9 + crates/revm/src/db/states.rs | 2 + crates/revm/src/db/states/bundle_account.rs | 11 +- crates/revm/src/db/states/bundle_state.rs | 8 + crates/revm/src/db/states/cache.rs | 16 ++ crates/revm/src/db/states/cache_account.rs | 9 + crates/revm/src/db/states/mem_usage.rs | 129 +++++++++++++ crates/revm/src/db/states/plain_account.rs | 14 ++ crates/revm/src/db/states/state.rs | 64 ++++++- .../revm/src/db/states/transition_account.rs | 3 + crates/revm/src/evm.rs | 11 ++ crates/revm/src/lib.rs | 3 + 23 files changed, 476 insertions(+), 69 deletions(-) create mode 100644 crates/revm/src/db/states/mem_usage.rs diff --git a/Cargo.lock b/Cargo.lock index 29a03cd699..b5fba9d155 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -739,6 +739,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "ctor" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e366bff8cd32dd8754b0991fb66b279dc48f598c3a18914852a6673deef583" +dependencies = [ + "quote", + "syn 2.0.48", +] + [[package]] name = "data-encoding" version = "2.5.0" @@ -2325,9 +2335,11 @@ dependencies = [ "ethers-core", "ethers-providers", "futures", + "hashbrown", "indicatif", "revm-interpreter", "revm-precompile", + "revm-utils", "serde", "serde_json", "tokio", @@ -2338,6 +2350,7 @@ name = "revm-interpreter" version = "3.0.0" dependencies = [ "revm-primitives", + "revm-utils", "serde", ] @@ -2373,6 +2386,7 @@ dependencies = [ "hashbrown", "hex", "once_cell", + "revm-utils", "serde", ] @@ -2390,6 +2404,18 @@ dependencies = [ "revm", ] +[[package]] +name = "revm-utils" +version = "0.1.0" +source = "git+https://github.com/megaeth-labs/reth-perf-utils.git#b9f0dac8d6195c2ab009907af8961798814b975e" +dependencies = [ + "allocator-api2", + "ctor", + "serde", + "serde_arrays", + "serde_json", +] + [[package]] name = "revme" version = "0.2.1" @@ -2728,6 +2754,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_arrays" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38636132857f68ec3d5f3eb121166d2af33cb55174c4d5ff645db6165cbef0fd" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" version = "1.0.196" diff --git a/Cargo.toml b/Cargo.toml index 426d6e403f..d192a3b296 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,9 @@ default-members = ["crates/revm"] all-features = true rustdoc-args = ["--cfg", "docsrs"] +[workspace.dependencies] +revm-utils = { git = "https://github.com/megaeth-labs/reth-perf-utils.git" } + [profile.release] lto = true codegen-units = 1 diff --git a/crates/interpreter/Cargo.toml b/crates/interpreter/Cargo.toml index afa42a6a4e..b2a7f6b999 100644 --- a/crates/interpreter/Cargo.toml +++ b/crates/interpreter/Cargo.toml @@ -21,6 +21,7 @@ serde = { version = "1.0", default-features = false, features = [ "derive", "rc", ], optional = true } +revm-utils = { workspace = true, optional = true } [features] default = ["std"] @@ -55,3 +56,5 @@ optional_eip3607 = ["revm-primitives/optional_eip3607"] optional_gas_refund = ["revm-primitives/optional_gas_refund"] optional_no_base_fee = ["revm-primitives/optional_no_base_fee"] optional_beneficiary_reward = ["revm-primitives/optional_beneficiary_reward"] +enable_opcode_metrics = ["revm-utils"] +enable_cache_record = ["revm-primitives/enable_cache_record"] diff --git a/crates/interpreter/src/instructions/arithmetic.rs b/crates/interpreter/src/instructions/arithmetic.rs index 724f5ff9c3..b18205fdd1 100644 --- a/crates/interpreter/src/instructions/arithmetic.rs +++ b/crates/interpreter/src/instructions/arithmetic.rs @@ -67,7 +67,10 @@ pub fn mulmod(interpreter: &mut Interpreter, _host: &mut H) { pub fn exp(interpreter: &mut Interpreter, _host: &mut H) { pop_top!(interpreter, op1, op2); - gas_or_fail!(interpreter, gas::exp_cost::(*op2)); + let cost = gas::exp_cost::(*op2); + gas_or_fail!(interpreter, cost); + #[cfg(feature = "enable_opcode_metrics")] + revm_utils::metrics::record_gas(crate::opcode::EXP, cost.unwrap_or(0)); *op2 = op1.pow(*op2); } diff --git a/crates/interpreter/src/instructions/host.rs b/crates/interpreter/src/instructions/host.rs index 4919b644a6..c1efb60f71 100644 --- a/crates/interpreter/src/instructions/host.rs +++ b/crates/interpreter/src/instructions/host.rs @@ -19,17 +19,17 @@ pub fn balance(interpreter: &mut Interpreter, host: &mut H) interpreter.instruction_result = InstructionResult::FatalExternalError; return; }; - gas!( - interpreter, - if SPEC::enabled(ISTANBUL) { - // EIP-1884: Repricing for trie-size-dependent opcodes - gas::account_access_gas::(is_cold) - } else if SPEC::enabled(TANGERINE) { - 400 - } else { - 20 - } - ); + let cost = if SPEC::enabled(ISTANBUL) { + // EIP-1884: Repricing for trie-size-dependent opcodes + gas::account_access_gas::(is_cold) + } else if SPEC::enabled(TANGERINE) { + 400 + } else { + 20 + }; + gas!(interpreter, cost); + #[cfg(feature = "enable_opcode_metrics")] + revm_utils::metrics::record_gas(crate::opcode::BALANCE, cost); push!(interpreter, balance); } @@ -50,21 +50,20 @@ pub fn extcodesize(interpreter: &mut Interpreter, host: &mu interpreter.instruction_result = InstructionResult::FatalExternalError; return; }; - if SPEC::enabled(BERLIN) { - gas!( - interpreter, - if is_cold { - COLD_ACCOUNT_ACCESS_COST - } else { - WARM_STORAGE_READ_COST - } - ); + let cost = if SPEC::enabled(BERLIN) { + if is_cold { + COLD_ACCOUNT_ACCESS_COST + } else { + WARM_STORAGE_READ_COST + } } else if SPEC::enabled(TANGERINE) { - gas!(interpreter, 700); + 700 } else { - gas!(interpreter, 20); - } - + 20 + }; + gas!(interpreter, cost); + #[cfg(feature = "enable_opcode_metrics")] + revm_utils::metrics::record_gas(crate::opcode::EXTCODESIZE, cost); push!(interpreter, U256::from(code.len())); } @@ -76,20 +75,20 @@ pub fn extcodehash(interpreter: &mut Interpreter, host: &mu interpreter.instruction_result = InstructionResult::FatalExternalError; return; }; - if SPEC::enabled(BERLIN) { - gas!( - interpreter, - if is_cold { - COLD_ACCOUNT_ACCESS_COST - } else { - WARM_STORAGE_READ_COST - } - ); + let cost = if SPEC::enabled(BERLIN) { + if is_cold { + COLD_ACCOUNT_ACCESS_COST + } else { + WARM_STORAGE_READ_COST + } } else if SPEC::enabled(ISTANBUL) { - gas!(interpreter, 700); + 700 } else { - gas!(interpreter, 400); - } + 400 + }; + gas!(interpreter, cost); + #[cfg(feature = "enable_opcode_metrics")] + revm_utils::metrics::record_gas(crate::opcode::EXTCODEHASH, cost); push_b256!(interpreter, code_hash); } @@ -103,10 +102,10 @@ pub fn extcodecopy(interpreter: &mut Interpreter, host: &mu }; let len = as_usize_or_fail!(interpreter, len_u256); - gas_or_fail!( - interpreter, - gas::extcodecopy_cost::(len as u64, is_cold) - ); + let cost = gas::extcodecopy_cost::(len as u64, is_cold); + gas_or_fail!(interpreter, cost); + #[cfg(feature = "enable_opcode_metrics")] + revm_utils::metrics::record_gas(crate::opcode::EXTCODECOPY, cost.unwrap_or(0)); if len == 0 { return; } @@ -146,7 +145,10 @@ pub fn sload(interpreter: &mut Interpreter, host: &mut H) { interpreter.instruction_result = InstructionResult::FatalExternalError; return; }; - gas!(interpreter, gas::sload_cost::(is_cold)); + let cost = gas::sload_cost::(is_cold); + gas!(interpreter, cost); + #[cfg(feature = "enable_opcode_metrics")] + revm_utils::metrics::record_gas(crate::opcode::SLOAD, cost); push!(interpreter, value); } @@ -160,10 +162,13 @@ pub fn sstore(interpreter: &mut Interpreter, host: &mut H) interpreter.instruction_result = InstructionResult::FatalExternalError; return; }; - gas_or_fail!(interpreter, { + let cost = { let remaining_gas = interpreter.gas.remaining(); gas::sstore_cost::(original, old, new, remaining_gas, is_cold) - }); + }; + gas_or_fail!(interpreter, cost); + #[cfg(feature = "enable_opcode_metrics")] + revm_utils::metrics::record_gas(crate::opcode::SSTORE, cost.unwrap_or(0)); refund!(interpreter, gas::sstore_refund::(original, old, new)); } @@ -195,7 +200,22 @@ pub fn log(interpreter: &mut Interpreter, host: &mut H) pop!(interpreter, offset, len); let len = as_usize_or_fail!(interpreter, len); - gas_or_fail!(interpreter, gas::log_cost(N as u8, len as u64)); + let cost = gas::log_cost(N as u8, len as u64); + gas_or_fail!(interpreter, cost); + #[cfg(feature = "enable_opcode_metrics")] + { + use crate::opcode::*; + let opcode = match N { + 0 => LOG0, + 1 => LOG1, + 2 => LOG2, + 3 => LOG3, + 4 => LOG4, + _ => unreachable!(), + }; + revm_utils::metrics::record_gas(opcode, cost.unwrap_or(0)); + } + let data = if len == 0 { Bytes::new() } else { @@ -236,7 +256,10 @@ pub fn selfdestruct(interpreter: &mut Interpreter, host: &m if !SPEC::enabled(LONDON) && !res.previously_destroyed { refund!(interpreter, gas::SELFDESTRUCT) } - gas!(interpreter, gas::selfdestruct_cost::(res)); + let cost = gas::selfdestruct_cost::(res); + gas!(interpreter, cost); + #[cfg(feature = "enable_opcode_metrics")] + revm_utils::metrics::record_gas(crate::opcode::SELFDESTRUCT, cost); interpreter.instruction_result = InstructionResult::SelfDestruct; } @@ -248,9 +271,12 @@ pub fn create( check_staticcall!(interpreter); // EIP-1014: Skinny CREATE2 - if IS_CREATE2 { + let _opcode: u8 = if IS_CREATE2 { check!(interpreter, PETERSBURG); - } + crate::opcode::CREATE2 + } else { + crate::opcode::CREATE + }; pop!(interpreter, value, code_offset, len); let len = as_usize_or_fail!(interpreter, len); @@ -270,7 +296,10 @@ pub fn create( interpreter.instruction_result = InstructionResult::CreateInitCodeSizeLimit; return; } - gas!(interpreter, gas::initcode_cost(len as u64)); + let cost = gas::initcode_cost(len as u64); + gas!(interpreter, cost); + #[cfg(feature = "enable_opcode_metrics")] + revm_utils::metrics::record_gas(_opcode, cost); } let code_offset = as_usize_or_fail!(interpreter, code_offset); @@ -281,10 +310,15 @@ pub fn create( // EIP-1014: Skinny CREATE2 let scheme = if IS_CREATE2 { pop!(interpreter, salt); - gas_or_fail!(interpreter, gas::create2_cost(len)); + let cost = gas::create2_cost(len); + gas_or_fail!(interpreter, cost); + #[cfg(feature = "enable_opcode_metrics")] + revm_utils::metrics::record_gas(_opcode, cost.unwrap_or(0)); CreateScheme::Create2 { salt } } else { gas!(interpreter, gas::CREATE); + #[cfg(feature = "enable_opcode_metrics")] + revm_utils::metrics::record_gas(_opcode, gas::CREATE); CreateScheme::Create }; @@ -296,6 +330,8 @@ pub fn create( gas_limit -= gas_limit / 64 } gas!(interpreter, gas_limit); + #[cfg(feature = "enable_opcode_metrics")] + revm_utils::metrics::record_gas(_opcode, gas_limit); // Call host to interact with target contract interpreter.next_action = InterpreterAction::Create { @@ -334,11 +370,14 @@ pub fn call(interpreter: &mut Interpreter, host: &mut H) { local_gas_limit, true, true, + crate::opcode::CALL, ) else { return; }; gas!(interpreter, gas_limit); + #[cfg(feature = "enable_opcode_metrics")] + revm_utils::metrics::record_gas(crate::opcode::CALL, gas_limit); // add call stipend if there is value to be transferred. if value != U256::ZERO { @@ -389,11 +428,14 @@ pub fn call_code(interpreter: &mut Interpreter, host: &mut local_gas_limit, true, false, + crate::opcode::CALLCODE, ) else { return; }; gas!(interpreter, gas_limit); + #[cfg(feature = "enable_opcode_metrics")] + revm_utils::metrics::record_gas(crate::opcode::CALLCODE, gas_limit); // add call stipend if there is value to be transferred. if value != U256::ZERO { @@ -436,13 +478,22 @@ pub fn delegate_call(interpreter: &mut Interpreter, host: & return; }; - let Some(gas_limit) = - calc_call_gas::(interpreter, host, to, false, local_gas_limit, false, false) - else { + let Some(gas_limit) = calc_call_gas::( + interpreter, + host, + to, + false, + local_gas_limit, + false, + false, + crate::opcode::DELEGATECALL, + ) else { return; }; gas!(interpreter, gas_limit); + #[cfg(feature = "enable_opcode_metrics")] + revm_utils::metrics::record_gas(crate::opcode::DELEGATECALL, gas_limit); // Call host to interact with target contract interpreter.next_action = InterpreterAction::Call { @@ -483,12 +534,21 @@ pub fn static_call(interpreter: &mut Interpreter, host: &mu return; }; - let Some(gas_limit) = - calc_call_gas::(interpreter, host, to, false, local_gas_limit, false, true) - else { + let Some(gas_limit) = calc_call_gas::( + interpreter, + host, + to, + false, + local_gas_limit, + false, + true, + crate::opcode::STATICCALL, + ) else { return; }; gas!(interpreter, gas_limit); + #[cfg(feature = "enable_opcode_metrics")] + revm_utils::metrics::record_gas(crate::opcode::STATICCALL, gas_limit); // Call host to interact with target contract interpreter.next_action = InterpreterAction::Call { diff --git a/crates/interpreter/src/instructions/host/call_helpers.rs b/crates/interpreter/src/instructions/host/call_helpers.rs index fc9509c25d..97992f26f8 100644 --- a/crates/interpreter/src/instructions/host/call_helpers.rs +++ b/crates/interpreter/src/instructions/host/call_helpers.rs @@ -42,6 +42,7 @@ pub fn calc_call_gas( local_gas_limit: u64, is_call_or_callcode: bool, is_call_or_staticcall: bool, + _opcode: u8, ) -> Option { let Some((is_cold, exist)) = host.load_account(to) else { interpreter.instruction_result = InstructionResult::FatalExternalError; @@ -58,6 +59,8 @@ pub fn calc_call_gas( ); gas!(interpreter, call_cost, None); + #[cfg(feature = "enable_opcode_metrics")] + revm_utils::metrics::record_gas(_opcode, call_cost); // EIP-150: Gas cost changes for IO-heavy operations let gas_limit = if SPEC::enabled(TANGERINE) { diff --git a/crates/interpreter/src/instructions/memory.rs b/crates/interpreter/src/instructions/memory.rs index 6a63eb6b03..21f38aaa97 100644 --- a/crates/interpreter/src/instructions/memory.rs +++ b/crates/interpreter/src/instructions/memory.rs @@ -42,7 +42,10 @@ pub fn mcopy(interpreter: &mut Interpreter, _host: &mut H) // into usize or fail let len = as_usize_or_fail!(interpreter, len); // deduce gas - gas_or_fail!(interpreter, gas::verylowcopy_cost(len as u64)); + let cost = gas::verylowcopy_cost(len as u64); + gas_or_fail!(interpreter, cost); + #[cfg(feature = "enable_opcode_metrics")] + revm_utils::metrics::record_gas(crate::opcode::MCOPY, cost.unwrap_or(0)); if len == 0 { return; } diff --git a/crates/interpreter/src/instructions/system.rs b/crates/interpreter/src/instructions/system.rs index 07df7a6604..d66e47914c 100644 --- a/crates/interpreter/src/instructions/system.rs +++ b/crates/interpreter/src/instructions/system.rs @@ -7,7 +7,10 @@ use crate::{ pub fn keccak256(interpreter: &mut Interpreter, _host: &mut H) { pop!(interpreter, from, len); let len = as_usize_or_fail!(interpreter, len); - gas_or_fail!(interpreter, gas::keccak256_cost(len as u64)); + let cost = gas::keccak256_cost(len as u64); + gas_or_fail!(interpreter, cost); + #[cfg(feature = "enable_opcode_metrics")] + revm_utils::metrics::record_gas(crate::opcode::KECCAK256, cost.unwrap_or(0)); let hash = if len == 0 { KECCAK_EMPTY } else { @@ -37,7 +40,10 @@ pub fn codesize(interpreter: &mut Interpreter, _host: &mut H) { pub fn codecopy(interpreter: &mut Interpreter, _host: &mut H) { pop!(interpreter, memory_offset, code_offset, len); let len = as_usize_or_fail!(interpreter, len); - gas_or_fail!(interpreter, gas::verylowcopy_cost(len as u64)); + let cost = gas::verylowcopy_cost(len as u64); + gas_or_fail!(interpreter, cost); + #[cfg(feature = "enable_opcode_metrics")] + revm_utils::metrics::record_gas(crate::opcode::CODECOPY, cost.unwrap_or(0)); if len == 0 { return; } @@ -83,7 +89,10 @@ pub fn callvalue(interpreter: &mut Interpreter, _host: &mut H) { pub fn calldatacopy(interpreter: &mut Interpreter, _host: &mut H) { pop!(interpreter, memory_offset, data_offset, len); let len = as_usize_or_fail!(interpreter, len); - gas_or_fail!(interpreter, gas::verylowcopy_cost(len as u64)); + let cost = gas::verylowcopy_cost(len as u64); + gas_or_fail!(interpreter, cost); + #[cfg(feature = "enable_opcode_metrics")] + revm_utils::metrics::record_gas(crate::opcode::CALLDATACOPY, cost.unwrap_or(0)); if len == 0 { return; } @@ -115,7 +124,10 @@ pub fn returndatacopy(interpreter: &mut Interpreter, _host: check!(interpreter, BYZANTIUM); pop!(interpreter, memory_offset, offset, len); let len = as_usize_or_fail!(interpreter, len); - gas_or_fail!(interpreter, gas::verylowcopy_cost(len as u64)); + let cost = gas::verylowcopy_cost(len as u64); + gas_or_fail!(interpreter, cost); + #[cfg(feature = "enable_opcode_metrics")] + revm_utils::metrics::record_gas(crate::opcode::RETURNDATACOPY, cost.unwrap_or(0)); let data_offset = as_usize_saturated!(offset); let (data_end, overflow) = data_offset.overflowing_add(len); if overflow || data_end > interpreter.return_data_buffer.len() { diff --git a/crates/interpreter/src/interpreter.rs b/crates/interpreter/src/interpreter.rs index 4cddf3ca6d..3094f5c9d0 100644 --- a/crates/interpreter/src/interpreter.rs +++ b/crates/interpreter/src/interpreter.rs @@ -288,6 +288,8 @@ impl Interpreter { { // Get current opcode. let opcode = unsafe { *self.instruction_pointer }; + #[cfg(feature = "enable_opcode_metrics")] + let _opcode = revm_utils::metrics::OpcodeExecuteRecord::new(opcode); // SAFETY: In analysis we are doing padding of bytecode so that we are sure that last // byte instruction is STOP so we are safe to just increment program_counter bcs on last instruction @@ -295,7 +297,7 @@ impl Interpreter { self.instruction_pointer = unsafe { self.instruction_pointer.offset(1) }; // execute instruction. - (instruction_table[opcode as usize])(self, host) + (instruction_table[opcode as usize])(self, host); } /// Take memory and replace it with empty memory. @@ -317,6 +319,8 @@ impl Interpreter { self.instruction_result = InstructionResult::Continue; self.shared_memory = shared_memory; // main loop + #[cfg(feature = "enable_opcode_metrics")] + revm_utils::metrics::start_record_op(); while self.instruction_result == InstructionResult::Continue { self.step(instruction_table, host); } diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index e59afd1100..953b289288 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -41,6 +41,7 @@ serde = { version = "1.0", default-features = false, features = [ "derive", "rc", ], optional = true } +revm-utils = { workspace = true, optional = true } [build-dependencies] hex = { version = "0.4", default-features = false } @@ -97,3 +98,4 @@ c-kzg = [ "dep:blst", "blst?/portable", ] +enable_cache_record = ["revm-utils"] diff --git a/crates/primitives/src/state.rs b/crates/primitives/src/state.rs index 8f6736bd79..5531980e5a 100644 --- a/crates/primitives/src/state.rs +++ b/crates/primitives/src/state.rs @@ -9,7 +9,15 @@ pub type State = HashMap; pub type TransientStorage = HashMap<(Address, U256), U256>; /// An account's Storage is a mapping from 256-bit integer keys to [StorageSlot]s. +#[cfg(not(feature = "enable_cache_record"))] pub type Storage = HashMap; +#[cfg(feature = "enable_cache_record")] +pub type Storage = HashMap< + U256, + StorageSlot, + hashbrown::hash_map::DefaultHashBuilder, + revm_utils::TrackingAllocator, +>; #[derive(Debug, Clone, PartialEq, Eq, Default)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -55,7 +63,10 @@ impl Account { pub fn new_not_existing() -> Self { Self { info: AccountInfo::default(), + #[cfg(not(feature = "enable_cache_record"))] storage: HashMap::new(), + #[cfg(feature = "enable_cache_record")] + storage: HashMap::new_in(revm_utils::TrackingAllocator), status: AccountStatus::LoadedAsNotExisting, } } @@ -129,7 +140,10 @@ impl From for Account { fn from(info: AccountInfo) -> Self { Self { info, + #[cfg(not(feature = "enable_cache_record"))] storage: HashMap::new(), + #[cfg(feature = "enable_cache_record")] + storage: HashMap::new_in(revm_utils::TrackingAllocator), status: AccountStatus::Loaded, } } diff --git a/crates/revm/Cargo.toml b/crates/revm/Cargo.toml index 40ddb2623b..95a3bf7d0e 100644 --- a/crates/revm/Cargo.toml +++ b/crates/revm/Cargo.toml @@ -40,6 +40,10 @@ ethers-providers = { version = "2.0", optional = true } ethers-core = { version = "2.0", optional = true } futures = { version = "0.3.30", optional = true } +# metric +revm-utils = { workspace = true, optional = true } +hashbrown = {version = "0.14", optional = true } + [dev-dependencies] ethers-contract = { version = "2.0.13", default-features = false } anyhow = "1.0.79" @@ -103,6 +107,11 @@ optional_beneficiary_reward = ["revm-interpreter/optional_beneficiary_reward"] secp256k1 = ["revm-precompile/secp256k1"] c-kzg = ["revm-precompile/c-kzg"] +# support perf test +enable_opcode_metrics = ["revm-interpreter/enable_opcode_metrics"] +enable_cache_record = ["revm-utils", "hashbrown", "revm-interpreter/enable_cache_record"] +enable_transact_measure = ["revm-utils"] + [[example]] name = "fork_ref_transact" path = "../../examples/fork_ref_transact.rs" diff --git a/crates/revm/src/db/states.rs b/crates/revm/src/db/states.rs index 2c58f2d632..8147e3c04f 100644 --- a/crates/revm/src/db/states.rs +++ b/crates/revm/src/db/states.rs @@ -4,6 +4,8 @@ pub mod bundle_state; pub mod cache; pub mod cache_account; pub mod changes; +#[cfg(feature = "enable_cache_record")] +pub mod mem_usage; pub mod plain_account; pub mod reverts; pub mod state; diff --git a/crates/revm/src/db/states/bundle_account.rs b/crates/revm/src/db/states/bundle_account.rs index 4c86210024..acc01c4873 100644 --- a/crates/revm/src/db/states/bundle_account.rs +++ b/crates/revm/src/db/states/bundle_account.rs @@ -93,7 +93,16 @@ impl BundleAccount { AccountInfoRevert::DeleteIt => { self.info = None; if self.original_info.is_none() { - self.storage = HashMap::new(); + #[cfg(not(feature = "enable_cache_record"))] + { + self.storage = HashMap::new(); + } + + #[cfg(feature = "enable_cache_record")] + { + self.storage = HashMap::new_in(revm_utils::TrackingAllocator); + } + return true; } else { // set all storage to zero but preserve original values. diff --git a/crates/revm/src/db/states/bundle_state.rs b/crates/revm/src/db/states/bundle_state.rs index f524b08ecb..0e5aa61b19 100644 --- a/crates/revm/src/db/states/bundle_state.rs +++ b/crates/revm/src/db/states/bundle_state.rs @@ -641,12 +641,20 @@ impl BundleState { Entry::Vacant(entry) => { // create empty account that we will revert on. // Only place where this account is not existing is if revert is DeleteIt. + #[cfg(not(feature = "enable_cache_record"))] let mut account = BundleAccount::new( None, None, HashMap::new(), AccountStatus::LoadedNotExisting, ); + #[cfg(feature = "enable_cache_record")] + let mut account = BundleAccount::new( + None, + None, + HashMap::new_in(revm_utils::TrackingAllocator), + AccountStatus::LoadedNotExisting, + ); if !account.revert(revert_account) { self.state_size += account.size_hint(); entry.insert(account); diff --git a/crates/revm/src/db/states/cache.rs b/crates/revm/src/db/states/cache.rs index a3232029ad..59888d402b 100644 --- a/crates/revm/src/db/states/cache.rs +++ b/crates/revm/src/db/states/cache.rs @@ -2,9 +2,13 @@ use super::{ plain_account::PlainStorage, transition_account::TransitionAccount, CacheAccount, PlainAccount, }; use alloc::vec::Vec; +#[cfg(feature = "enable_cache_record")] +use hashbrown::hash_map::DefaultHashBuilder; use revm_interpreter::primitives::{ Account, AccountInfo, Address, Bytecode, HashMap, State as EVMState, B256, }; +#[cfg(feature = "enable_cache_record")] +use revm_utils::TrackingAllocator; /// Cache state contains both modified and original values. /// @@ -15,10 +19,16 @@ use revm_interpreter::primitives::{ #[derive(Clone, Debug, PartialEq, Eq)] pub struct CacheState { /// Block state account with account state + #[cfg(not(feature = "enable_cache_record"))] pub accounts: HashMap, + #[cfg(feature = "enable_cache_record")] + pub accounts: HashMap, /// created contracts /// TODO add bytecode counter for number of bytecodes added/removed. + #[cfg(not(feature = "enable_cache_record"))] pub contracts: HashMap, + #[cfg(feature = "enable_cache_record")] + pub contracts: HashMap, /// Has EIP-161 state clear enabled (Spurious Dragon hardfork). pub has_state_clear: bool, } @@ -33,8 +43,14 @@ impl CacheState { /// New default state. pub fn new(has_state_clear: bool) -> Self { Self { + #[cfg(not(feature = "enable_cache_record"))] accounts: HashMap::default(), + #[cfg(feature = "enable_cache_record")] + accounts: HashMap::new_in(TrackingAllocator), + #[cfg(not(feature = "enable_cache_record"))] contracts: HashMap::default(), + #[cfg(feature = "enable_cache_record")] + contracts: HashMap::new_in(TrackingAllocator), has_state_clear, } } diff --git a/crates/revm/src/db/states/cache_account.rs b/crates/revm/src/db/states/cache_account.rs index 126ce2ffe7..ea1ff0facc 100644 --- a/crates/revm/src/db/states/cache_account.rs +++ b/crates/revm/src/db/states/cache_account.rs @@ -162,7 +162,10 @@ impl CacheAccount { status: self.status, previous_info, previous_status, + #[cfg(not(feature = "enable_cache_record"))] storage: HashMap::default(), + #[cfg(feature = "enable_cache_record")] + storage: HashMap::new_in(revm_utils::TrackingAllocator), storage_was_destroyed: true, }) } @@ -186,7 +189,10 @@ impl CacheAccount { status: self.status, previous_info, previous_status, + #[cfg(not(feature = "enable_cache_record"))] storage: HashMap::new(), + #[cfg(feature = "enable_cache_record")] + storage: HashMap::new_in(revm_utils::TrackingAllocator), storage_was_destroyed: true, }) } @@ -259,7 +265,10 @@ impl CacheAccount { status: self.status, previous_info, previous_status, + #[cfg(not(feature = "enable_cache_record"))] storage: HashMap::new(), + #[cfg(feature = "enable_cache_record")] + storage: HashMap::new_in(revm_utils::TrackingAllocator), storage_was_destroyed: false, }, ) diff --git a/crates/revm/src/db/states/mem_usage.rs b/crates/revm/src/db/states/mem_usage.rs new file mode 100644 index 0000000000..8fe941a9c2 --- /dev/null +++ b/crates/revm/src/db/states/mem_usage.rs @@ -0,0 +1,129 @@ +//! This module defines the method for obtaining the memory size occupied by the State. +use super::{ + cache::CacheState, transition_account::TransitionAccount, BundleAccount, BundleState, + CacheAccount, State, TransitionState, +}; +use revm_interpreter::primitives::{db::Database, AccountInfo}; + +/// This trait is used to support types in obtaining the dynamically allocated memory +/// size used by them +pub trait DynMemUsage { + fn dyn_mem_usage(&self) -> usize; +} + +impl DynMemUsage for AccountInfo { + fn dyn_mem_usage(&self) -> usize { + self.code.as_ref().map(|c| c.len()).unwrap_or(0) + } +} + +impl DynMemUsage for CacheAccount { + fn dyn_mem_usage(&self) -> usize { + self.account + .as_ref() + .map(|a| a.info.dyn_mem_usage()) + .unwrap_or(0) + } +} + +impl DynMemUsage for CacheState { + fn dyn_mem_usage(&self) -> usize { + let accounts_dyn_size = self + .accounts + .iter() + .map(|(_k, v)| v.dyn_mem_usage()) + .sum::(); + let contracts_dyn_size = self.contracts.iter().map(|(_k, v)| v.len()).sum::(); + accounts_dyn_size + contracts_dyn_size + } +} + +impl DynMemUsage for TransitionAccount { + fn dyn_mem_usage(&self) -> usize { + let info_dyn_size = self.info.as_ref().map(|a| a.dyn_mem_usage()).unwrap_or(0); + + let pre_info_dyn_size = self + .previous_info + .as_ref() + .map(|a| a.dyn_mem_usage()) + .unwrap_or(0); + + info_dyn_size + pre_info_dyn_size + } +} + +impl DynMemUsage for TransitionState { + fn dyn_mem_usage(&self) -> usize { + self.transitions + .iter() + .map(|(_k, v)| v.dyn_mem_usage()) + .sum::() + } +} + +impl DynMemUsage for BundleAccount { + fn dyn_mem_usage(&self) -> usize { + let info_dyn_size = self.info.as_ref().map(|v| v.dyn_mem_usage()).unwrap_or(0); + let original_info_dyn_size = self + .original_info + .as_ref() + .map(|v| v.dyn_mem_usage()) + .unwrap_or(0); + info_dyn_size + original_info_dyn_size + } +} + +impl DynMemUsage for BundleState { + fn dyn_mem_usage(&self) -> usize { + let state_dyn_size = self + .state + .iter() + .map(|(_, v)| v.dyn_mem_usage()) + .sum::(); + let contracts_dyn_size = self.contracts.iter().map(|(_, v)| v.len()).sum::(); + state_dyn_size + contracts_dyn_size + } +} + +impl State { + fn dyn_mem_size(&self) -> usize { + // Calculate the memory size of the State on the heap (excluding the HashMap section). + let cache = self.cache.dyn_mem_usage(); + let transaction_state = self + .transition_state + .as_ref() + .map(|v| v.dyn_mem_usage() + std::mem::size_of::()) + .unwrap_or(0); + let bundle_state = self.bundle_state.dyn_mem_usage(); + // block_hashes is a BTreeMap, and here we use the following formula to estimate its + // memory usage: + // memory_size = ( sizeof(key) + sizeof(value) ) * block_hashes.len() + let block_hashes = self.block_hashes.len() * (64 + 32); + + // The size of the hashmap calculated using a memory allocator. + let map_size = revm_utils::allocator::stats().diff as usize; + + // Total dynamic memory size. + let total_dyn_size = cache + transaction_state + bundle_state + block_hashes + map_size; + println!("cache_heap_size: {:?}", cache); + println!("transaction_size: {:?}", transaction_state); + println!("bundle_state: {:?}", bundle_state); + println!("block_hashes_size: {:?}", block_hashes); + println!("map_size: {:?}", map_size); + println!("total_dyn_size: {:?}", total_dyn_size); + + total_dyn_size + } + + fn static_mem_size(&self) -> usize { + let state_size = std::mem::size_of::>(); + println!("state_size: {:?}", state_size); + state_size + } + + pub fn mem_usage(&self) -> usize { + let total = self.dyn_mem_size() + self.static_mem_size(); + println!("total_size: {:?}", total); + total + } +} diff --git a/crates/revm/src/db/states/plain_account.rs b/crates/revm/src/db/states/plain_account.rs index f6cb76618c..d28a1cd703 100644 --- a/crates/revm/src/db/states/plain_account.rs +++ b/crates/revm/src/db/states/plain_account.rs @@ -1,4 +1,8 @@ +#[cfg(feature = "enable_cache_record")] +use hashbrown::hash_map::DefaultHashBuilder; use revm_interpreter::primitives::{AccountInfo, HashMap, StorageSlot, U256}; +#[cfg(feature = "enable_cache_record")] +use revm_utils::TrackingAllocator; // TODO rename this to BundleAccount. As for the block level we have original state. #[derive(Clone, Debug, Default, PartialEq, Eq)] @@ -23,17 +27,27 @@ impl PlainAccount { /// This storage represent values that are before block changed. /// /// Note: Storage that we get EVM contains original values before t +#[cfg(not(feature = "enable_cache_record"))] pub type StorageWithOriginalValues = HashMap; +#[cfg(feature = "enable_cache_record")] +pub type StorageWithOriginalValues = + HashMap; /// Simple plain storage that does not have previous value. /// This is used for loading from database, cache and for bundle state. +#[cfg(not(feature = "enable_cache_record"))] pub type PlainStorage = HashMap; +#[cfg(feature = "enable_cache_record")] +pub type PlainStorage = HashMap; impl From for PlainAccount { fn from(info: AccountInfo) -> Self { Self { info, + #[cfg(not(feature = "enable_cache_record"))] storage: HashMap::new(), + #[cfg(feature = "enable_cache_record")] + storage: HashMap::new_in(TrackingAllocator), } } } diff --git a/crates/revm/src/db/states/state.rs b/crates/revm/src/db/states/state.rs index ec82cb87ff..02e44aec9e 100644 --- a/crates/revm/src/db/states/state.rs +++ b/crates/revm/src/db/states/state.rs @@ -62,6 +62,11 @@ pub struct State { impl State { /// Return the builder that build the State. pub fn builder() -> StateBuilder { + #[cfg(feature = "enable_cache_record")] + { + revm_utils::allocator::reset(); + println!("Reset HashMap size of State!"); + } StateBuilder::default() } } @@ -179,21 +184,45 @@ impl State { if let Some(account) = self.bundle_state.account(&address).cloned().map(Into::into) { + #[cfg(feature = "enable_cache_record")] + let _record = + revm_utils::HitRecord::new(revm_utils::Function::LoadCacheAccount); return Ok(entry.insert(account)); } } + #[cfg(feature = "enable_cache_record")] + let _record = revm_utils::MissRecord::new(revm_utils::Function::LoadCacheAccount); // if not found in bundle, load it from database let info = self.database.basic(address)?; let account = match info { None => CacheAccount::new_loaded_not_existing(), Some(acc) if acc.is_empty() => { - CacheAccount::new_loaded_empty_eip161(HashMap::new()) + #[cfg(not(feature = "enable_cache_record"))] + let ret = CacheAccount::new_loaded_empty_eip161(HashMap::new()); + #[cfg(feature = "enable_cache_record")] + let ret = CacheAccount::new_loaded_empty_eip161(HashMap::new_in( + revm_utils::TrackingAllocator, + )); + ret + } + Some(acc) => { + #[cfg(not(feature = "enable_cache_record"))] + let ret = CacheAccount::new_loaded(acc, HashMap::new()); + #[cfg(feature = "enable_cache_record")] + let ret = CacheAccount::new_loaded( + acc, + HashMap::new_in(revm_utils::TrackingAllocator), + ); + ret } - Some(acc) => CacheAccount::new_loaded(acc, HashMap::new()), }; Ok(entry.insert(account)) } - hash_map::Entry::Occupied(entry) => Ok(entry.into_mut()), + hash_map::Entry::Occupied(entry) => { + #[cfg(feature = "enable_cache_record")] + let _record = revm_utils::HitRecord::new(revm_utils::Function::LoadCacheAccount); + Ok(entry.into_mut()) + } } } @@ -221,14 +250,22 @@ impl Database for State { fn code_by_hash(&mut self, code_hash: B256) -> Result { let res = match self.cache.contracts.entry(code_hash) { - hash_map::Entry::Occupied(entry) => Ok(entry.get().clone()), + hash_map::Entry::Occupied(entry) => { + #[cfg(feature = "enable_cache_record")] + let _record = revm_utils::HitRecord::new(revm_utils::Function::CodeByHash); + Ok(entry.get().clone()) + } hash_map::Entry::Vacant(entry) => { if self.use_preloaded_bundle { if let Some(code) = self.bundle_state.contracts.get(&code_hash) { + #[cfg(feature = "enable_cache_record")] + let _record = revm_utils::HitRecord::new(revm_utils::Function::CodeByHash); entry.insert(code.clone()); return Ok(code.clone()); } } + #[cfg(feature = "enable_cache_record")] + let _record = revm_utils::MissRecord::new(revm_utils::Function::CodeByHash); // if not found in bundle ask database let code = self.database.code_by_hash(code_hash)?; entry.insert(code.clone()); @@ -248,13 +285,22 @@ impl Database for State { .account .as_mut() .map(|account| match account.storage.entry(index) { - hash_map::Entry::Occupied(entry) => Ok(*entry.get()), + hash_map::Entry::Occupied(entry) => { + #[cfg(feature = "enable_cache_record")] + let _record = revm_utils::HitRecord::new(revm_utils::Function::Storage); + Ok(*entry.get()) + } hash_map::Entry::Vacant(entry) => { // if account was destroyed or account is newly built // we return zero and don't ask database. let value = if is_storage_known { + #[cfg(feature = "enable_cache_record")] + let _record = revm_utils::HitRecord::new(revm_utils::Function::Storage); U256::ZERO } else { + #[cfg(feature = "enable_cache_record")] + let _record = + revm_utils::MissRecord::new(revm_utils::Function::Storage); self.database.storage(address, index)? }; entry.insert(value); @@ -272,8 +318,14 @@ impl Database for State { // block number is never bigger then u64::MAX. let u64num: u64 = number.to(); match self.block_hashes.entry(u64num) { - btree_map::Entry::Occupied(entry) => Ok(*entry.get()), + btree_map::Entry::Occupied(entry) => { + #[cfg(feature = "enable_cache_record")] + let _record = revm_utils::HitRecord::new(revm_utils::Function::BlockHash); + Ok(*entry.get()) + } btree_map::Entry::Vacant(entry) => { + #[cfg(feature = "enable_cache_record")] + let _record = revm_utils::MissRecord::new(revm_utils::Function::BlockHash); let ret = *entry.insert(self.database.block_hash(number)?); // prune all hashes that are older then BLOCK_HASH_HISTORY diff --git a/crates/revm/src/db/states/transition_account.rs b/crates/revm/src/db/states/transition_account.rs index 1dc6bc33dd..6c39ed45ef 100644 --- a/crates/revm/src/db/states/transition_account.rs +++ b/crates/revm/src/db/states/transition_account.rs @@ -139,7 +139,10 @@ impl TransitionAccount { BundleAccount { info: self.previous_info.clone(), original_info: self.previous_info.clone(), + #[cfg(not(feature = "enable_cache_record"))] storage: StorageWithOriginalValues::new(), + #[cfg(feature = "enable_cache_record")] + storage: StorageWithOriginalValues::new_in(revm_utils::TrackingAllocator), status: self.previous_status, } } diff --git a/crates/revm/src/evm.rs b/crates/revm/src/evm.rs index 9db4c50854..134d7c00b7 100644 --- a/crates/revm/src/evm.rs +++ b/crates/revm/src/evm.rs @@ -158,6 +158,9 @@ impl Evm<'_, EXT, DB> { /// This function will validate the transaction. #[inline] pub fn transact(&mut self) -> EVMResult { + #[cfg(feature = "enable_transact_measure")] + revm_utils::metrics::transact_record(); + self.handler.validation().env(&self.context.evm.env)?; let initial_gas_spend = self .handler @@ -166,6 +169,8 @@ impl Evm<'_, EXT, DB> { self.handler .validation() .tx_against_state(&mut self.context)?; + #[cfg(feature = "enable_transact_measure")] + revm_utils::metrics::preverify_transaction_inner_record(); let output = self.transact_preverified_inner(initial_gas_spend); self.handler.post_execution().end(&mut self.context, output) @@ -311,6 +316,8 @@ impl Evm<'_, EXT, DB> { /// Transact pre-verified transaction. fn transact_preverified_inner(&mut self, initial_gas_spend: u64) -> EVMResult { + #[cfg(feature = "enable_transact_measure")] + revm_utils::metrics::transact_sub_record(); let ctx = &mut self.context; let pre_exec = self.handler.pre_execution(); @@ -325,6 +332,8 @@ impl Evm<'_, EXT, DB> { pre_exec.deduct_caller(ctx)?; let gas_limit = ctx.evm.env.tx.gas_limit - initial_gas_spend; + #[cfg(feature = "enable_transact_measure")] + revm_utils::metrics::before_execute_record(); let exec = self.handler.execution(); // call inner handling of call/create @@ -344,6 +353,8 @@ impl Evm<'_, EXT, DB> { FrameOrResult::Frame(first_frame) => self.start_the_loop(first_frame), FrameOrResult::Result(result) => result, }; + #[cfg(feature = "enable_transact_measure")] + let _record = revm_utils::metrics::ExecuteEndRecord::new(); let ctx = &mut self.context; diff --git a/crates/revm/src/lib.rs b/crates/revm/src/lib.rs index 2b2b161174..1ed9cd8c1e 100644 --- a/crates/revm/src/lib.rs +++ b/crates/revm/src/lib.rs @@ -54,3 +54,6 @@ pub use revm_interpreter as interpreter; pub use revm_interpreter::primitives; #[doc(inline)] pub use revm_precompile as precompile; + +#[cfg(feature = "enable_opcode_metrics")] +pub use revm_interpreter::opcode::{self as revm_opcode, OpCode};