Skip to content
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

Convert rvgo's memory implementation to radix trie #83

Open
wants to merge 12 commits into
base: master
Choose a base branch
from

Conversation

mininny
Copy link
Collaborator

@mininny mininny commented Oct 3, 2024

// InvalidateNode invalidates the hash cache along the path to the specified address.
InvalidateNode(addr uint64)
// GenerateProof generates the Merkle proof for the given address.
GenerateProof(addr uint64) [][32]byte
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

from @protolambda :

You could add a slice as argument, to append the result to, and then return the updated slice header. Then you can allocate a slice with the capacity of the expected proof size once, and write to that in each proof level, to prevent allocations.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I modified the function so that GenerateProof takes a slice as argument, and each radix node fills in the proof slice at the correct index. The proof slice is only allocated once in the initial generateProof call (make([][32]byte, 60)) to reduce the allocations: c5a040b

@codecov-commenter
Copy link

codecov-commenter commented Oct 3, 2024

Codecov Report

Attention: Patch coverage is 86.56716% with 36 lines in your changes missing coverage. Please review.

Project coverage is 73.17%. Comparing base (636210a) to head (f263b31).
Report is 20 commits behind head on master.

Files with missing lines Patch % Lines
rvgo/fast/radix.go 88.47% 15 Missing and 13 partials ⚠️
rvgo/fast/page.go 55.55% 8 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master      #83      +/-   ##
==========================================
+ Coverage   72.30%   73.17%   +0.86%     
==========================================
  Files          16       17       +1     
  Lines        2723     2923     +200     
==========================================
+ Hits         1969     2139     +170     
- Misses        670      689      +19     
- Partials       84       95      +11     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

largeDataset = 1_000_000
)

func BenchmarkMemoryOperations(b *testing.B) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Adding a new file for benchmarking would be good. Also, why these methods are implemented as test? Do they validate something?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I moved the benchmarking to a new file: memory_benchmarking_test.go.
I'm not sure if I understand why you mean by implemented as test. If you're talking about testing.B, it's because the benchmarking capabilities are offered under testing package. Also, there is no validation in the benchmarking test, we only measure the function calls themselves.

data := make([]byte, 1)
b.ResetTimer()
for i := 0; i < b.N; i++ {
addr := mathrand.Uint64() % 1000000 // Confined to a smaller range
Copy link
Collaborator

Choose a reason for hiding this comment

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

We can use mathrand.Intn

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Nice, thanks!

if p, ok := m.pages[pageIndex]; ok {
return p.GenerateProof(addr) // Generate proof from the page.
} else {
return zeroHashRange(0, 8) // Return zero hashes if the page does not exist.
Copy link
Collaborator

Choose a reason for hiding this comment

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

May I ask why is zeroHashRange(0, 8) ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It's because the number of proof for a page is 8, where 0 is the leaf page data and 7 is the page's merkle root. I've also added this as a comment.

}
}

if depth > 16 {
Copy link
Collaborator

Choose a reason for hiding this comment

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

what about the range between 8 < depth <= 16?

Copy link
Collaborator Author

@mininny mininny Oct 5, 2024

Choose a reason for hiding this comment

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

Sorry, the correct code should be depth > 9.
depth 0~7: the intermediate merkle hashes.
depth 8: the leaf nodes pointing to the children nodes
depth 9~: invalid gindex.

// Build the radix trie path to the new page, creating nodes as necessary.
radixLevel1 := m.radix
if (*radixLevel1).Children[branchPaths[0]] == nil {
node := &SmallRadixNode[L3]{Depth: 4}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why are we starting at L3, not L2 or L1?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The L1 is the radix root, and it's type is SmallRadixNode[L2]. This [L2] points to the children node's type.
When we allocate the children of a L1, the L2's children type is SmallRadixNode[L3], where the [L3] is the L2's children type.
So, it seems like we're skipping L2, but the SmallRadixNode[L3] itself is the alias of L2.


radixLevel2 := (*radixLevel1).Children[branchPaths[0]]
if (*radixLevel2).Children[branchPaths[1]] == nil {
node := &SmallRadixNode[L4]{Depth: 8}
Copy link
Collaborator

Choose a reason for hiding this comment

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

These Depth value can be evaluated using branchFactors: [10]uint64{4, 4, 4, 4, 4, 4, 4, 8, 8, 8},

require.Equal(t, r1, r2, "expecting manual page combination to match subtree merkle func")
})

//t.Run("random few pages", func(t *testing.T) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why are this test commented out?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I forgot to uncomment & fix the test after I've changed the implementation. I uncommented it and fixed the code :)

@mininny mininny force-pushed the feature/mininny/rvgo-radix-memory branch from c5a040b to f263b31 Compare October 5, 2024 22:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants