-
Notifications
You must be signed in to change notification settings - Fork 4
Avoid allocations when building a tree #29
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
Conversation
noamnelke
left a comment
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.
Looks great, added some minor improvement suggestions.
| // of a leaf whose membership in the tree is being proven, but the given node isn't. | ||
| if parent.OnProvenPath { | ||
| if lChild.OnProvenPath || rChild.OnProvenPath { | ||
| if !lChild.OnProvenPath { | ||
| t.proof = append(t.proof, lChild.value) | ||
| copy := append([]byte(nil), lChild.value...) | ||
| t.proof = append(t.proof, copy) | ||
| } | ||
| if !rChild.OnProvenPath { | ||
| t.proof = append(t.proof, rChild.value) | ||
| copy := append([]byte(nil), rChild.value...) | ||
| t.proof = append(t.proof, copy) | ||
| } | ||
| } |
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.
Perhaps it would be clearer like this?
if rChild.OnProvenPath && !lChild.OnProvenPath {
copy := append([]byte(nil), lChild.value...)
t.proof = append(t.proof, copy)
}
if lChild.OnProvenPath && !rChild.OnProvenPath {
copy := append([]byte(nil), rChild.value...)
t.proof = append(t.proof, copy)
}If the t.proof was a flat byte slice this code would be cleaner, but not top priority if it's hard to do.
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.
Thanks!
If the t.proof was a flat byte slice this code would be cleaner, but not top priority if it's hard to do.
I was also thinking about this. But it's more complicated and changes the public API so I left it for the future.
merkle.go
Outdated
|
|
||
| l.parking.value = nil | ||
| parent := t.calcParent(t.parentBuf[:0], lChild, rChild) | ||
| t.parentBuf = parent.value |
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 assignment seems redundant. In fact I don't understand why t.calcParent needs to get a buffer at all. It seems that it should always use t.parentBuf directly since it has access to it.
There are only two uses of t.calcParent and I don't think there's a reason not to use t.parentBuf for the second usage, too (here you use nil in the other instance, but I don't see a reason to, as Tree is not designed to be thread-safe anyway).
Am I missing something?
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 think you can also just assign the result of t.calcParent directly into n a few lines below, not a big deal, though.
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 didn't use t.parentBuf in RootAndProof as it complicated it without any perf improvement. The calculated parent needed to be copied anyway in every code path.
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.
- What I meant is that inside the implementation of
t.calcParentyou can passt.parentBufintot.hashdirectly, instead of accepting it as an argument. - Why do this
t.parentBuf = parent.value? Why not just delete this line? (it's nowt.parentBuf = n.value)
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.
Ad 1.
Because I don't want to reuse t.parentBuf in RootAndProof method (hence it passes nil). It only complicated the code there.
Ad 2.
It needs to reassign because the buffer passed into t.hash() will be reallocated the first time it is called (we don't know the size of the required buffer upfront to preallocate it or even use an array). It's the same story as with buf = append(buf, ...).
Reuse layer parking buffer
It avoids reallocating memory for layer parking by not discarding the parking when it is not needed anymore (it sets slice length to 0 instead of assigning a
nil).Allow the user to reuse memory that was passed to
Tree::AddLeaf.This is achieved by copying the passed data to the parking instead of just assigning the slice.
Allow reusing memory for calculating the hash of a parent
This is achieved by changing the
shared.HashFuncto accept a memory buffer into which the hash can be directly calculated. There is a permanent parent buffer (Tree::parentBuf) to keep the memory around.