Skip to content

Conversation

@jrchatruc
Copy link
Collaborator

@jrchatruc jrchatruc commented Oct 20, 2025

Motivation

Jumps are noticeably slow compared to most of the VM. This is due to lazy computation of invalid jump destinations.

Description

Instead of lazy computation of blocklist, do greedy computation of allowlist and store the result, fetch it with the DB.

Closes #4951

Comparison of gigagas/s in hoodi between one node synced on this branch (orange) and three others on main:

Screenshot 2025-10-20 at 4 46 20 PM

Missing:

  • Some Revm compatibility fails to compile;
  • There's some unneeded unsafe code, it can be converted to a simple copy, there's no evidence that overhead is problematic;
  • The type needs documenting, maybe make the fields private so most users can only create through from_bytecode;
  • A shortcut that receives the hash may be useful as well, to reuse some hashes in snap sync.
  • Check there are no redundant computations as it was done in a hurry, places that seemed irrelevant to execution just compute the needed data in place;
  • Tests and benchmarks need updating.

@github-actions
Copy link

github-actions bot commented Oct 20, 2025

Lines of code report

Total lines added: 144
Total lines removed: 30
Total lines changed: 174

Detailed view
+------------------------------------------------------------------------+-------+------+
| File                                                                   | Lines | Diff |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/blockchain/vm.rs                                         | 103   | -1   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/common/constants.rs                                      | 40    | -6   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/common/trie/node_hash.rs                                 | 104   | -5   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/common/trie/trie.rs                                      | 951   | -3   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/common/types/account.rs                                  | 224   | +47  |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/common/types/account_update.rs                           | 43    | +2   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/l2/networking/rpc/l2/transaction.rs                      | 244   | +1   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/l2/prover/src/guest_program/src/execution.rs             | 411   | +9   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/networking/p2p/sync.rs                                   | 1363  | +7   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/networking/rpc/eth/account.rs                            | 262   | +1   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/storage/rlp.rs                                           | 36    | -2   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/storage/store.rs                                         | 1526  | -3   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/storage/store_db/in_memory.rs                            | 628   | -1   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/storage/store_db/rocksdb.rs                              | 1444  | +37  |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/db.rs                                                 | 15    | -1   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/bench/revm_comparison/src/levm_bench.rs          | 85    | +14  |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/runner/src/input.rs                              | 118   | +1   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/call_frame.rs                                | 352   | -3   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/db/gen_db.rs                                 | 325   | -1   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/db/mod.rs                                    | 13    | -1   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/execution_handlers.rs                        | 124   | +1   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/hooks/default_hook.rs                        | 350   | +3   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/hooks/l2_hook.rs                             | 166   | +6   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/opcode_handlers/environment.rs               | 309   | +9   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/opcode_handlers/push.rs                      | 38    | +1   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/opcode_handlers/stack_memory_storage_flow.rs | 248   | -1   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/opcode_handlers/system.rs                    | 803   | +1   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/precompiles.rs                               | 1567  | -1   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/witness_db.rs                                         | 83    | -1   |
+------------------------------------------------------------------------+-------+------+
| ethrex/tooling/ef_tests/state/runner/revm_db.rs                        | 202   | +4   |
+------------------------------------------------------------------------+-------+------+

@Oppen Oppen changed the title Perf/store jump targets perf(levm): store valid jump targets with code Oct 20, 2025
@github-actions github-actions bot added levm Lambda EVM implementation performance labels Oct 20, 2025
@edg-l edg-l self-assigned this Oct 21, 2025
@github-actions
Copy link

github-actions bot commented Oct 21, 2025

Benchmark Block Execution Results Comparison Against Main

Command Mean [s] Min [s] Max [s] Relative
base 81.394 ± 0.174 81.199 81.684 1.04 ± 0.00
head 78.493 ± 0.096 78.388 78.662 1.00

// Hash value for an empty trie, equal to keccak(RLP_NULL)
pub static ref EMPTY_TRIE_HASH: H256 = H256::from_slice(
Keccak256::new()
&Keccak256::new()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This and other changes like this are to silence a deprecated notice from sha

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

@edg-l
Copy link
Contributor

edg-l commented Oct 21, 2025

The size diff appears large due to a cargo.lock update in the tooling/bench code adding +3,819 lines, the pr is not big

@github-actions
Copy link

Benchmark for bdbc6fc

Click to view benchmark
Test Base PR %
Trie/cita-trie insert 10k 35.5±0.57ms 35.9±1.34ms +1.13%
Trie/cita-trie insert 1k 3.5±0.01ms 3.6±0.18ms +2.86%
Trie/ethrex-trie insert 10k 66.3±0.66ms 66.7±0.93ms +0.60%
Trie/ethrex-trie insert 1k 8.4±0.05ms 8.4±0.04ms 0.00%

@github-actions
Copy link

Benchmark for 16909a5

Click to view benchmark
Test Base PR %
Trie/cita-trie insert 10k 35.7±1.30ms 38.0±1.45ms +6.44%
Trie/cita-trie insert 1k 3.5±0.02ms 3.6±0.20ms +2.86%
Trie/ethrex-trie insert 10k 66.7±1.43ms 68.0±1.12ms +1.95%
Trie/ethrex-trie insert 1k 8.4±0.08ms 8.4±0.03ms 0.00%

Comment on lines 80 to 85
# Run hyperfine benchmark, removing the database before each run.
# Pipe `yes` into the remove command to automatically confirm.
hyperfine -w 5 -N -r 10 --show-output --export-markdown "bench_pr_comparison.md" \
-L bin "$BINS" -n "{bin}" \
--prepare "yes | ./bin/ethrex-{bin} removedb" \
"./bin/ethrex-{bin} --network fixtures/genesis/perf-ci.json --force import ./fixtures/blockchain/l2-1k-erc20.rlp"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The command was using removedb as a setup for each batch of runs, but the command was cancelled due to having no input. I changed it to use --prepare instead, which runs before each single run, and piped yes into it to automatically confirm.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was reverted, but we should fix this if we intend to keep this benchmark.

@github-actions
Copy link

Benchmark for 96dee34

Click to view benchmark
Test Base PR %
Trie/cita-trie insert 10k 33.1±2.07ms 37.8±1.92ms +14.20%
Trie/cita-trie insert 1k 3.5±0.03ms 3.6±0.03ms +2.86%
Trie/ethrex-trie insert 10k 69.4±2.89ms 67.1±2.63ms -3.31%
Trie/ethrex-trie insert 1k 8.6±0.19ms 8.4±0.21ms -2.33%

@github-actions
Copy link

Benchmark for bda8527

Click to view benchmark
Test Base PR %
Trie/cita-trie insert 10k 34.8±0.37ms 34.5±0.17ms -0.86%
Trie/cita-trie insert 1k 3.5±0.01ms 3.6±0.09ms +2.86%
Trie/ethrex-trie insert 10k 66.1±0.29ms 65.7±0.55ms -0.61%
Trie/ethrex-trie insert 1k 8.4±0.04ms 8.4±0.08ms 0.00%

@github-actions
Copy link

Benchmark for 947e30a

Click to view benchmark
Test Base PR %
Trie/cita-trie insert 10k 32.8±1.70ms 38.8±1.33ms +18.29%
Trie/cita-trie insert 1k 3.5±0.02ms 3.5±0.02ms 0.00%
Trie/ethrex-trie insert 10k 69.6±1.32ms 68.8±1.47ms -1.15%
Trie/ethrex-trie insert 1k 8.4±0.14ms 8.5±0.10ms +1.19%

@github-project-automation github-project-automation bot moved this to In Review in ethrex_l1 Oct 23, 2025
@jrchatruc jrchatruc enabled auto-merge October 23, 2025 18:13
Comment on lines +283 to +287
let codes_hashed = guest_program_state
.codes_hashed
.iter()
.map(|(h, c)| (*h, c.bytecode.to_vec()))
.collect();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Many of these conversions seem unneeded. We should check that they don't affect the performance of the L2.

let code_value = AccountCodeRLP::from(code).bytes().clone();
batch.put_cf(&cf_codes, code_key, code_value);
let mut buf =
Vec::with_capacity(6 + code.bytecode.len() + 2 * code.jump_targets.len());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs a comment

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, can't we implement RLPEncode for Code instead? Or put this in a helper.

@github-actions
Copy link

Benchmark for 9d3432d

Click to view benchmark
Test Base PR %
Trie/cita-trie insert 10k 35.7±2.27ms 29.6±2.50ms -17.09%
Trie/cita-trie insert 1k 2.8±0.01ms 2.8±0.09ms 0.00%
Trie/ethrex-trie insert 10k 67.4±1.31ms 69.6±1.44ms +3.26%
Trie/ethrex-trie insert 1k 7.6±0.12ms 7.6±0.07ms 0.00%

.collect::<Vec<u8>>()
.as_slice()
.encode(&mut buf);
self.write_async(CF_ACCOUNT_CODES, hash_key, buf).await
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to update the documentation of CF_ACCOUNT_CODES

@github-actions
Copy link

Benchmark for f562e67

Click to view benchmark
Test Base PR %
Trie/cita-trie insert 10k 35.2±0.80ms 35.8±1.17ms +1.70%
Trie/cita-trie insert 1k 3.5±0.01ms 3.6±0.15ms +2.86%
Trie/ethrex-trie insert 10k 66.2±0.68ms 66.7±1.75ms +0.76%
Trie/ethrex-trie insert 1k 8.4±0.04ms 8.4±0.02ms 0.00%

let Some(bytes) = self.read_sync(CF_ACCOUNT_CODES, hash_key)? else {
return Ok(None);
};
let bytes = Bytes::from_owner(bytes);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for this variable.

.map(|bytes| AccountCodeRLP::from_bytes(bytes).to())
.transpose()
.map_err(StoreError::from)
let Some(bytes) = self.read_sync(CF_ACCOUNT_CODES, hash_key)? else {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could use the get_pinned_cf rocksdb method here to avoid copies before deserializing.

Comment on lines +1160 to +1172
let (bytecode, targets) = decode_bytes(&bytes)?;
let (targets, rest) = decode_bytes(targets)?;
if !rest.is_empty() || !targets.len().is_multiple_of(2) {
return Err(StoreError::DecodeError);
}
let code = Code {
hash: code_hash,
bytecode: Bytes::copy_from_slice(bytecode),
jump_targets: targets
.chunks_exact(2)
.map(|c| u16::from_le_bytes([c[0], c[1]]))
.collect(),
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be an RLPDecode implementation.

// Store updated code in DB
if let Some(code) = &update.code {
self.add_account_code(info.code_hash, code.clone()).await?;
self.add_account_code(code.clone()).await?;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not introduced by this PR, but this clone isn't needed in the underlying Store implementation, since it only needs a reference for serialization.

Comment on lines +262 to +263
.bytecode
.bytecode
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks kind of weird

let address = word_to_address(self.current_call_frame.stack.pop1()?);
let address_was_cold = !self.substate.add_accessed_address(address);
let account_code_length = self.db.get_account_code(address)?.len().into();
// FIXME: a bit wasteful to fetch the whole code just to get the length.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only use of this opcode I've seen is to check if an account is an EOA (post-7702). If it's an EOA, then SENDER and CALLER are the same, and EXTCODESIZE is 23, but later we also need to fetch the code anyways, so any cache would negate the cost of this use.

Comment on lines +295 to +299
call_frame
.bytecode
.jump_targets
.binary_search(&jump_address)
.is_ok()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should go in Code

pub hash: H256,
pub bytecode: Bytes,
// TODO: Consider using Arc<[u16]> (needs to enable serde rc feature)
pub jump_targets: Vec<u16>,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs a comment explaining the reason for using u16 instead of bigger numbers. Linking to https://eips.ethereum.org/EIPS/eip-170 and https://eips.ethereum.org/EIPS/eip-7907. Also, EIP-7907 might break this since it bumps initcode max size to 96Kb.

}

fn compute_jump_targets(code: &[u8]) -> Vec<u16> {
debug_assert!(code.len() <= u16::MAX as usize);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs a comment

Copy link
Collaborator

@MegaRedHand MegaRedHand left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Let's refactor and add comments in a following PR

@jrchatruc jrchatruc added this pull request to the merge queue Oct 23, 2025
Merged via the queue into main with commit 669700c Oct 23, 2025
53 checks passed
@jrchatruc jrchatruc deleted the perf/store_jump_targets branch October 23, 2025 19:17
@github-project-automation github-project-automation bot moved this from Todo to Done in ethrex_performance Oct 23, 2025
@github-project-automation github-project-automation bot moved this from In Review to Done in ethrex_l1 Oct 23, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

levm Lambda EVM implementation performance

Projects

Status: Done
Status: Done

Development

Successfully merging this pull request may close these issues.

Jumpdest pre-processing on create

5 participants