Skip to content

refactor(levm): remove clones for account structs#2684

Merged
SDartayet merged 26 commits into
mainfrom
remove-clones
May 21, 2025
Merged

refactor(levm): remove clones for account structs#2684
SDartayet merged 26 commits into
mainfrom
remove-clones

Conversation

@SDartayet

Copy link
Copy Markdown
Contributor

Motivation

Improving the performance of some cases by avoiding clones where possible.

Description

Many clones of account structs were removed. This involved changing the output of the get_account and access_account functions of the DB to return a reference to an account, as well as refactorings of the code which involved these functions.

Resolves #2611

@github-actions

github-actions Bot commented May 6, 2025

Copy link
Copy Markdown

Lines of code report

Total lines added: 37
Total lines removed: 2
Total lines changed: 39

Detailed view
+----------------------------------------------------------+-------+------+
| File                                                     | Lines | Diff |
+----------------------------------------------------------+-------+------+
| ethrex/crates/vm/backends/levm/mod.rs                    | 642   | +4   |
+----------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/db/cache.rs                    | 31    | +3   |
+----------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/db/gen_db.rs                   | 198   | -2   |
+----------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/hooks/default_hook.rs          | 338   | +3   |
+----------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/hooks/l2_hook.rs               | 139   | +3   |
+----------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/opcode_handlers/environment.rs | 306   | +10  |
+----------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/opcode_handlers/system.rs      | 771   | +14  |
+----------------------------------------------------------+-------+------+

@github-actions

github-actions Bot commented May 6, 2025

Copy link
Copy Markdown

Benchmark Results Comparison

PR Results

Benchmark Results: Factorial

Command Mean [ms] Min [ms] Max [ms] Relative
revm_Factorial 246.5 ± 6.5 242.4 264.4 1.00
levm_Factorial 819.3 ± 4.1 814.0 826.7 3.32 ± 0.09

Benchmark Results: Factorial - Recursive

Command Mean [s] Min [s] Max [s] Relative
revm_FactorialRecursive 1.562 ± 0.089 1.438 1.679 1.00
levm_FactorialRecursive 12.992 ± 0.046 12.933 13.066 8.32 ± 0.47

Benchmark Results: Fibonacci

Command Mean [ms] Min [ms] Max [ms] Relative
revm_Fibonacci 219.1 ± 4.7 214.1 230.9 1.00
levm_Fibonacci 820.4 ± 6.7 809.7 829.9 3.74 ± 0.09

Benchmark Results: ManyHashes

Command Mean [ms] Min [ms] Max [ms] Relative
revm_ManyHashes 8.7 ± 0.1 8.6 8.8 1.00
levm_ManyHashes 17.3 ± 0.1 17.2 17.5 1.99 ± 0.02

Benchmark Results: BubbleSort

Command Mean [s] Min [s] Max [s] Relative
revm_BubbleSort 3.251 ± 0.040 3.214 3.350 1.00
levm_BubbleSort 5.701 ± 0.479 5.476 7.047 1.75 ± 0.15

Benchmark Results: ERC20 - Transfer

Command Mean [ms] Min [ms] Max [ms] Relative
revm_ERC20Transfer 250.7 ± 2.1 248.4 254.9 1.00
levm_ERC20Transfer 503.9 ± 4.9 497.0 513.1 2.01 ± 0.03

Benchmark Results: ERC20 - Mint

Command Mean [ms] Min [ms] Max [ms] Relative
revm_ERC20Mint 143.6 ± 5.1 140.6 156.0 1.00
levm_ERC20Mint 322.7 ± 2.4 319.3 325.8 2.25 ± 0.08

Benchmark Results: ERC20 - Approval

Command Mean [s] Min [s] Max [s] Relative
revm_ERC20Approval 1.045 ± 0.012 1.033 1.067 1.00
levm_ERC20Approval 1.905 ± 0.023 1.886 1.964 1.82 ± 0.03

Main Results

Benchmark Results: Factorial

Command Mean [ms] Min [ms] Max [ms] Relative
revm_Factorial 242.3 ± 1.6 240.7 246.4 1.00
levm_Factorial 820.2 ± 9.5 809.2 836.6 3.39 ± 0.04

Benchmark Results: Factorial - Recursive

Command Mean [s] Min [s] Max [s] Relative
revm_FactorialRecursive 1.496 ± 0.074 1.424 1.628 1.00
levm_FactorialRecursive 13.014 ± 0.104 12.917 13.289 8.70 ± 0.44

Benchmark Results: Fibonacci

Command Mean [ms] Min [ms] Max [ms] Relative
revm_Fibonacci 216.7 ± 6.9 213.0 236.2 1.00
levm_Fibonacci 820.9 ± 13.0 810.4 846.8 3.79 ± 0.14

Benchmark Results: ManyHashes

Command Mean [ms] Min [ms] Max [ms] Relative
revm_ManyHashes 9.1 ± 0.1 8.9 9.3 1.00
levm_ManyHashes 17.3 ± 0.4 17.1 18.4 1.91 ± 0.05

Benchmark Results: BubbleSort

Command Mean [s] Min [s] Max [s] Relative
revm_BubbleSort 3.300 ± 0.040 3.263 3.378 1.00
levm_BubbleSort 5.507 ± 0.042 5.459 5.589 1.67 ± 0.02

Benchmark Results: ERC20 - Transfer

Command Mean [ms] Min [ms] Max [ms] Relative
revm_ERC20Transfer 253.9 ± 1.6 251.6 256.6 1.00
levm_ERC20Transfer 500.4 ± 3.9 494.5 506.6 1.97 ± 0.02

Benchmark Results: ERC20 - Mint

Command Mean [ms] Min [ms] Max [ms] Relative
revm_ERC20Mint 143.7 ± 2.0 142.1 148.1 1.00
levm_ERC20Mint 320.7 ± 2.2 318.3 323.7 2.23 ± 0.03

Benchmark Results: ERC20 - Approval

Command Mean [s] Min [s] Max [s] Relative
revm_ERC20Approval 1.057 ± 0.011 1.046 1.082 1.00
levm_ERC20Approval 1.882 ± 0.008 1.872 1.899 1.78 ± 0.02

@SDartayet
SDartayet marked this pull request as ready for review May 9, 2025 14:22
@SDartayet
SDartayet requested a review from a team as a code owner May 9, 2025 14:22
@SDartayet SDartayet added performance Block execution throughput and performance in general and removed performance Block execution throughput and performance in general labels May 9, 2025

@JereSalo JereSalo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The advantage of not cloning the Account when getting it comes from not cloning it's storage I think.
Maybe sometimes we are better off by doing let info = vm.db.get_account(sender_address)?.info.clone() rather than adding lines of code to make assignations to variables in some cases just because performance will probably not be affected from that clone and it is a little bit more concise.

I like the initiative of not cloning Account when doing get_account() but we need to find a way that is the simplest and adds the least amount of code I think. Maybe cloning AccountInfo if we need both nonce and balance is not a bad idea. Other ideas are welcome too for improving simplicity.
Let me know what you think. This PR looks good though. Good job :)

Comment thread crates/vm/db.rs Outdated
Comment on lines +79 to +93
// If the account already exists, we can just update its info in the array and avoid cloning it
if let Some(account) = self.accounts.get_mut(&update.address) {
account.nonce = info.nonce;
account.balance = info.balance;
account.code_hash = info.code_hash;
} else {
let account_info = AccountInfo {
nonce: info.nonce,
balance: info.balance,
code_hash: info.code_hash,
};

//Update the account info
self.accounts.insert(update.address, account_info);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Honestly here I'd just do:
self.accounts.insert(update.address, info.clone());
It is way more simple and we are just cloning account info, which is pretty small. I think it is worth it for making the code simpler because the perfromance difference is negligible I'd say.

We should optimize for performance when cloning an entire Account that has storage in it. But these cases in which we clone AccountInfo are probably not significant. And we prefer simplicity in this scenario.
What do you think?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Agree given that it should be pretty small and we want to go for simplicity. Also it will be better for isolating the changes, if we want to test the performance impact of removing the clones of AccountInfo we can do it in another branch

Comment thread crates/vm/levm/src/hooks/default_hook.rs Outdated
Comment thread crates/vm/levm/src/hooks/l2_hook.rs Outdated
Comment thread crates/vm/levm/src/opcode_handlers/environment.rs Outdated
Comment thread crates/vm/levm/src/opcode_handlers/environment.rs Outdated
Comment thread crates/vm/levm/src/opcode_handlers/system.rs Outdated
Comment thread crates/vm/levm/src/opcode_handlers/system.rs Outdated
Comment thread crates/vm/levm/src/opcode_handlers/system.rs Outdated
Comment thread crates/vm/levm/src/opcode_handlers/system.rs Outdated

@rodrigo-o rodrigo-o left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looking great, agreed with the comment regarding simplicity and the cloning of account info, we can create a small PR later with this if we still find it impacts performance.

Comment thread crates/vm/db.rs Outdated
Comment on lines +79 to +93
// If the account already exists, we can just update its info in the array and avoid cloning it
if let Some(account) = self.accounts.get_mut(&update.address) {
account.nonce = info.nonce;
account.balance = info.balance;
account.code_hash = info.code_hash;
} else {
let account_info = AccountInfo {
nonce: info.nonce,
balance: info.balance,
code_hash: info.code_hash,
};

//Update the account info
self.accounts.insert(update.address, account_info);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Agree given that it should be pretty small and we want to go for simplicity. Also it will be better for isolating the changes, if we want to test the performance impact of removing the clones of AccountInfo we can do it in another branch

@rodrigo-o

Copy link
Copy Markdown
Contributor

I like the initiative of not cloning Account when doing get_account() but we need to find a way that is the simplest and adds the least amount of code I think. Maybe cloning AccountInfo if we need both nonce and balance is not a bad idea. Other ideas are welcome too for improving simplicity.

We are about to start testing the difference between main and this branch in syncing, we will be able to see the improvements of this PR and if they are significant as we think we can go either with a bit of finetuning regarding some of the clones for simplicity or create an issue for tackling it immediatly after in a follow-up.

@SDartayet

SDartayet commented May 20, 2025

Copy link
Copy Markdown
Contributor Author

Performance Comparison: Syncing with Holesky

Run Details:

  • Network: Holesky
  • Starting Block: 967980
  • Block Batch Size: 512
  • Run Duration: ~20 minutes

Specs:

  • CPU: AMD Ryzen 5 3600
  • Memory: 62Gb
  • Operating System: Debian 12

Sync Performance:

Branch Avg. Batch Time Batches Executed Blocks executed Total execution Time. Notes
main 12m 9s 1 511 20m 31s Did not complete a second batch.
this branch ~2m 31s 6 3066 19m 51s

Average batch time for 'this branch' is calculated from the provided subsequent batch times.

Flamegraph Observations:

  • Flamegraphs show a significant reduction in clone operations on this branch compared to main.

Main branch flamegraph, zoomed in to see the clone operations:
image

This branch flamegraph:
image

@SDartayet SDartayet added performance Block execution throughput and performance in general levm Lambda EVM implementation labels May 20, 2025
@github-actions

github-actions Bot commented May 20, 2025

Copy link
Copy Markdown

Benchmark Block Execution Results Comparison Against Main

Command Mean [s] Min [s] Max [s] Relative
base 211.614 ± 1.292 210.244 214.055 1.00 ± 0.01
head 211.067 ± 0.781 209.642 212.058 1.00

@JereSalo JereSalo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

good pr, left some suggestions and I think it will be ready to merge afterwards

Comment thread crates/vm/levm/src/hooks/default_hook.rs Outdated
Comment thread crates/vm/levm/src/hooks/l2_hook.rs Outdated
Comment thread crates/vm/levm/src/opcode_handlers/environment.rs Outdated
Comment thread crates/vm/levm/src/opcode_handlers/system.rs Outdated
@SDartayet
SDartayet added this pull request to the merge queue May 21, 2025
Merged via the queue into main with commit 6632b44 May 21, 2025
@SDartayet
SDartayet deleted the remove-clones branch May 21, 2025 16:04
pedrobergamini pushed a commit to pedrobergamini/ethrex that referenced this pull request Aug 24, 2025
**Motivation**

Improving the performance of some cases by avoiding clones where
possible.

**Description**

Many clones of account structs were removed. This involved changing the
output of the get_account and access_account functions of the DB to
return a reference to an account, as well as refactorings of the code
which involved these functions.

Resolves [lambdaclass#2611](lambdaclass#2611)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

levm Lambda EVM implementation performance Block execution throughput and performance in general

Projects

None yet

Development

Successfully merging this pull request may close these issues.

LEVM: remove "clones" that can be avoided, especially with accounts

3 participants