-
Notifications
You must be signed in to change notification settings - Fork 13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
tests: add mpt vs vkt insertion benchmarks #146
Conversation
8b7f84d
to
00cb7c7
Compare
Signed-off-by: Ignacio Hagopian <[email protected]>
00cb7c7
to
4a3af34
Compare
) | ||
|
||
func BenchmarkTriesRandom(b *testing.B) { | ||
numAccounts := []int{1_000, 5_000, 10_000} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'll run the benchmark for 1000, 5000, and 10000 key-value insertions.
We can tune these cases if we have a more accurate ballpark.
numAccounts := []int{1_000, 5_000, 10_000} | ||
|
||
for _, numAccounts := range numAccounts { | ||
rs := rand.New(rand.NewSource(42)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the sake of consistency, let's try to make these benchmark deterministic.
|
||
for _, numAccounts := range numAccounts { | ||
rs := rand.New(rand.NewSource(42)) | ||
accounts := getRandomStateAccounts(rs, numAccounts) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a helper function I created to create random state accounts. I'll touch on this later in this file.
Note that the benchmark is for State Accounts. We could play also with storage slots, but I've the sense that most of the overhead will be the same in both. Maybe some nits difference in trie key generation; but as a first exploration I think state accounts are reasonable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure it's the same, because in most cases the account trie will be very small, whereas the verkle tree is always full.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yeah, that's a fair comment. I think I explained incorrectly that I meant to compare inserting a state account vs a storage slot in a VKT. In this new scenario, both insertion would be in the same tree so I'd expect their performance be the same.
It's true that if we compare it with MPT, then both cases aren't similar for the reason you said!
// Warmup VKT configuration | ||
trie.NewVerkleTrie(verkle.New(), trie.NewDatabase(memorydb.New())).TryUpdate([]byte("00000000000000000000000000000012"), []byte("B")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a warmup to let precomp
be generated if isn't there and not mess up with benchmark results.
prevTrie, _ := trieDB.OpenTrie(common.Hash{}) | ||
for k := 0; k < len(accounts); k++ { | ||
prevTrie.TryUpdateAccount(accounts[k].address[:], &accounts[k].stateAccount) | ||
} | ||
prevTrie.Commit(false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We pre-populate some random accounts in the trie and commit, which will persist everything in the underlying database.
} | ||
prevTrie.Commit(false) | ||
|
||
accounts := getRandomStateAccounts(rs, numAccounts) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We generate a new set of accounts (because if we use the previous set, it will match the same keys).
So the idea here is that there will be nodes that would overlap in the paths; just to make it more interesting.
|
||
func BenchmarkTriesRandomVKTStateless(b *testing.B) { | ||
numAccounts := []int{1_000, 5_000, 10_000} | ||
state.TestVKTOpenStateless = true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We set the flag mentioned before so whenever OpenTrie(...)
is called internally, it will open the verkle trie in stateless mode.
if TestVKTOpenStateless { | ||
r, err = verkle.ParseStatelessNode(payload, 0, root[:]) | ||
if err != nil { | ||
panic(err) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Getting this to work was interesting to understand the stateless.go
implementation in go-verkle.
I think this is the correct way to do what I intend. (Look at the second bullet of the PR description)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmmm, if you need to open a stateless node, you are using an older, slower version. Please rebase and re-run the benchmarks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh no, there wasn't a need for it. I just remembered some old pprof image you shared and I remembered seeing StatelessNodes
there, so wanted to also include a benchmark for that case.
If the replay-block benchmark isn't using a stateless VKT, I can remove that benchmark since isn't relevant anymore.
Is that the case?
(Actually, adding this stateless VKT benchmark was friction here, so glad if that isn't meaningful anymore)
(This PR is on top of the latest beverly-hills
so should be using the latest stuff)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something that might not have been obvious: StatelessNode
will disappear in the mid-term. I've got a PR that I need to dust-off, for which the code in tree.go
can do both stateful and stateless. In any case, stateful trees are more relevant to benchmark at the moment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gballet, great. I'll prune things a bit in this PR to avoid this flag, and remove the stateless benchmark.
for i := 0; i < b.N; i++ { | ||
trie, err := trieDB.OpenTrie(prevTrie.Hash()) | ||
if err != nil { | ||
b.Fatal(err) | ||
} | ||
for k := 0; k < len(accounts); k++ { | ||
trie.TryUpdateAccount(accounts[k].address[:], &accounts[k].stateAccount) | ||
} | ||
trie.Commit(true) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Run the same benchmark as the other, but the underlying VKT is stateless so it will be doing a lot more byte decoding.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, comparing to stateless is also interesting, I'm just saying that it's not as interesting as benchmarking stateful
Closing this PR since these benchmarks were moved to #147, where we're making performance improvements. |
This PR adds two benchmarks:
Running these benchmarks on my desktop computer shows significant gap in performance between the two:
I've been looking a bit closer on possible whys, and I've some ideas to start experimenting. But here we have at least the current baseline to try making the gap smaller.
Note that these benchmarks are 100% focused on comparing the tries performance. In a real block execution, the block execution is composed on many other tasks. For example, if trie insertions are 40% of the work of executing a block, then their performance difference would only impact ~40% of the block execution. (40% is an example).