From d89a149dc1f0aceebb5728d3d45a009c7566f5f5 Mon Sep 17 00:00:00 2001 From: Oleksandr Zarudnyi Date: Thu, 23 May 2024 16:53:54 +0700 Subject: [PATCH] feat: increase EVM interpreter opcode reruns to 32 (#26) --- benchmark_analyzer/src/benchmark/group/mod.rs | 17 +++++++++++------ benchmark_analyzer/src/benchmark/mod.rs | 13 ++++++------- .../src/benchmark_analyzer/arguments.rs | 6 +++--- .../matter_labs/test/metadata/evm_contract.rs | 13 +++++++++++-- .../src/directories/matter_labs/test/mod.rs | 3 ++- .../src/vm/eravm/system_contracts.rs | 2 +- era-contracts | 2 +- solidity | 2 +- system-contracts-stable-build | Bin 819088 -> 818860 bytes 9 files changed, 36 insertions(+), 22 deletions(-) diff --git a/benchmark_analyzer/src/benchmark/group/mod.rs b/benchmark_analyzer/src/benchmark/group/mod.rs index 199269f2..c117a4a2 100644 --- a/benchmark_analyzer/src/benchmark/group/mod.rs +++ b/benchmark_analyzer/src/benchmark/group/mod.rs @@ -56,6 +56,11 @@ impl Group { let mut ergs_total_candidate: u64 = 0; for (path, reference) in reference.elements.iter() { + if path.contains("tests/solidity/complex/interpreter/test.json") + && path.contains("#deployer") + { + continue; + } let candidate = match candidate.elements.get(path.as_str()) { Some(candidate) => candidate, None => continue, @@ -156,19 +161,19 @@ impl Group { /// Returns the EVM interpreter ergs/gas ratio. /// pub fn evm_interpreter_ratios(&self) -> Vec<(String, f64)> { - #[allow(clippy::unnecessary_to_owned)] - let elements: Vec<(String, Element)> = self.elements.to_owned().into_iter().collect(); let mut results = Vec::with_capacity(Benchmark::EVM_OPCODES.len()); for evm_opcode in Benchmark::EVM_OPCODES.into_iter() { let name_substring = format!("test.json::{evm_opcode}["); - let mut template_and_full: Vec<(String, Element)> = elements + let [full, template]: [Element; 2] = self + .elements .iter() .filter(|element| element.0.contains(name_substring.as_str())) .rev() .take(2) - .cloned() - .collect(); - let (full, template) = (template_and_full.remove(0).1, template_and_full.remove(0).1); + .map(|element| (element.1.to_owned())) + .collect::>() + .try_into() + .expect("Always valid"); let ergs_difference = full.ergs - template.ergs; let gas_difference = full.gas - template.gas; diff --git a/benchmark_analyzer/src/benchmark/mod.rs b/benchmark_analyzer/src/benchmark/mod.rs index ffaa73a1..ad724502 100644 --- a/benchmark_analyzer/src/benchmark/mod.rs +++ b/benchmark_analyzer/src/benchmark/mod.rs @@ -26,8 +26,8 @@ impl Benchmark { /// The EVM interpreter group identifier. pub const EVM_INTERPRETER_GROUP_NAME: &'static str = "EVMInterpreter"; - /// The EVM interpreter group identifier prefix. - pub const EVM_INTERPRETER_GROUP_PREFIX: &'static str = "EVMInterpreter M3B3"; + /// The EVM interpreter cycles group identifier. + pub const EVM_INTERPRETER_GROUP_NAME_CYCLES: &'static str = "EVMInterpreter M3B3"; /// The EVM opcodes to test. pub const EVM_OPCODES: [&'static str; 135] = [ @@ -159,12 +159,12 @@ impl Benchmark { "SWAP14", "SWAP15", "SWAP16", - "CREATE", "CALL", - "RETURN", - "DELEGATECALL", "STATICCALL", + "DELEGATECALL", + "CREATE", "CREATE2", + "RETURN", "REVERT", ]; @@ -181,8 +181,7 @@ impl Benchmark { }; let mut group_results = Group::compare(reference_group, candidate_group); - println!("group geomin {}", group_results.ergs_mean); - if group_name.starts_with(Self::EVM_INTERPRETER_GROUP_PREFIX) { + if group_name.starts_with(Self::EVM_INTERPRETER_GROUP_NAME_CYCLES) { if let (Some(reference_ratios), Some(candidate_ratios)) = ( reference .groups diff --git a/benchmark_analyzer/src/benchmark_analyzer/arguments.rs b/benchmark_analyzer/src/benchmark_analyzer/arguments.rs index 2af7f54e..ff99b301 100644 --- a/benchmark_analyzer/src/benchmark_analyzer/arguments.rs +++ b/benchmark_analyzer/src/benchmark_analyzer/arguments.rs @@ -13,11 +13,11 @@ use structopt::StructOpt; #[structopt(name = "benchmark-analyzer", about = "The zkEVM benchmark analyzer")] pub struct Arguments { /// The reference build benchmark. - #[structopt(long = "reference")] + #[structopt(long = "reference", default_value = "reference.json")] pub reference: PathBuf, /// The candidate build benchmark. - #[structopt(long = "candidate")] + #[structopt(long = "candidate", default_value = "candidate.json")] pub candidate: PathBuf, /// The output file. If unset, the result is printed to `stdout`. @@ -25,7 +25,7 @@ pub struct Arguments { pub output_path: Option, /// Maximum number of results displayed in a group. - #[structopt(short = "gm", long = "group-max", default_value = "100")] + #[structopt(long = "group-max", default_value = "100")] pub group_max: usize, } diff --git a/compiler_tester/src/directories/matter_labs/test/metadata/evm_contract.rs b/compiler_tester/src/directories/matter_labs/test/metadata/evm_contract.rs index 50eb7dd9..0bcf1005 100644 --- a/compiler_tester/src/directories/matter_labs/test/metadata/evm_contract.rs +++ b/compiler_tester/src/directories/matter_labs/test/metadata/evm_contract.rs @@ -14,6 +14,9 @@ pub struct EVMContract { } impl EVMContract { + /// The number of pattern reruns to provide more accurate benchmarks. + pub const RUNTIME_CODE_REPEATS: usize = 32; + /// /// Returns the init code. /// @@ -36,7 +39,13 @@ impl EVMContract { /// /// Returns the runtime code. /// - pub fn runtime_code(&self) -> String { - format!("{}00", self.runtime_code) + pub fn runtime_code(&self, instruction_name: &str) -> String { + let repeats = match instruction_name { + "RETURNDATASIZE" | "RETURNDATACOPY" | "EXTCODESIZE" | "EXTCODEHASH" | "EXTCODECOPY" + | "CALL" | "STATICCALL" | "DELEGATECALL" | "CREATE" | "CREATE2" => 1, + _ => Self::RUNTIME_CODE_REPEATS, + }; + + format!("{}00", self.runtime_code.repeat(repeats)) } } diff --git a/compiler_tester/src/directories/matter_labs/test/mod.rs b/compiler_tester/src/directories/matter_labs/test/mod.rs index 5d8df65b..bb73d04a 100644 --- a/compiler_tester/src/directories/matter_labs/test/mod.rs +++ b/compiler_tester/src/directories/matter_labs/test/mod.rs @@ -280,7 +280,8 @@ impl MatterLabsTest { let mut instances = BTreeMap::new(); for (instance, evm_contract) in self.metadata.evm_contracts.iter() { - let runtime_code = evm_contract.runtime_code(); + let instruction_name = instance.split('_').next().expect("Always exists"); + let runtime_code = evm_contract.runtime_code(instruction_name); let mut bytecode = evm_contract.init_code(runtime_code.len()); bytecode.push_str(runtime_code.as_str()); diff --git a/compiler_tester/src/vm/eravm/system_contracts.rs b/compiler_tester/src/vm/eravm/system_contracts.rs index 27cf88e2..1daf8fb3 100644 --- a/compiler_tester/src/vm/eravm/system_contracts.rs +++ b/compiler_tester/src/vm/eravm/system_contracts.rs @@ -107,7 +107,7 @@ impl SystemContracts { /// The base token system contract implementation path. const PATH_BASE_TOKEN: &'static str = - "era-contracts/system-contracts/contracts/L2BaseToken.sol:L2BaseToken"; + "era-contracts/system-contracts/contracts/L2EthToken.sol:L2EthToken"; /// The EVM gas manager system contract implementation path. const PATH_EVM_GAS_MANAGER: &'static str = diff --git a/era-contracts b/era-contracts index b8098420..19b81f7c 160000 --- a/era-contracts +++ b/era-contracts @@ -1 +1 @@ -Subproject commit b80984205b4f453ed1f21d7b8d497f6f6988574e +Subproject commit 19b81f7c06d089cb307bfbfba33dd794e3a4cb90 diff --git a/solidity b/solidity index 59cc93e5..028cf330 160000 --- a/solidity +++ b/solidity @@ -1 +1 @@ -Subproject commit 59cc93e5e6ff193ddbcccaa2d4c7ed81b992ede6 +Subproject commit 028cf3309437819503a37f60bfa8aad2af7fb9b7 diff --git a/system-contracts-stable-build b/system-contracts-stable-build index 9c108481e26ea952af225320a70bb37217adf53c..b8fab9b4023a3de27603209d8936b25adf96438e 100644 GIT binary patch delta 9516 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 delta 9201 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