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

Merge in btcd commit 6229e3583505a82d4514b1efa86f910b78693825 #381

Merged
merged 2 commits into from
Sep 24, 2016

Conversation

cjepson
Copy link
Contributor

@cjepson cjepson commented Sep 23, 2016

Merges in btcd commit 6229e35.

This commit drastically reduces the number of allocations needed to
deserialize a transaction and its scripts by using the combination of a
free list for initially deserializing the individual scripts along with
copying them into a single contiguous byte slice after the final size is
known and modifying each script in the transaction to point to its
location within the contiguous blob.

The end result is only a single allocation that holds all of the scripts
for a transaction regardless of the total number of scripts it has.

The script free list allows a maximum of 12,500 items with each buffer
being 512 bytes.  This implies it will have a peak usage of 6.1MB.  The
values were chosen based on profiling data and a desire to allow at
least 100 scripts per transaction to be simultaneously deserialized by
125 peers.

Also, while optimizing, decode directly into the existing previous
outpoint structure of each transaction input in order to avoid the extra
allocation per input that is otherwise caused when the local escapes to
the heap.

The following is a before and after comparison of the allocations
with the benchmarks that did not change removed:

benchmark              old allocs     new allocs     delta
-----------------------------------------------------------
ReadTxOut              1              0              -100.00%
ReadTxIn               2              0              -100.00%
DeserializeTxSmall     7              5              -28.57%
DeserializeTxLarge     11146          6              -99.95%
// because it is slightly more than twice the size of the vast majority
// of all "standard" scripts. Larger scripts are still deserialized
// properly as the free list will simply be bypassed for them.
freeListMaxScriptSize = 512
Copy link
Member

@davecgh davecgh Sep 23, 2016

Choose a reason for hiding this comment

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

Might want to consider tweaking this number since I don't believe the standard Decred transaction script size is the same as Bitcoin.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

They're very similar in size. I'm not sure it's required that they be tweaked, at least, not at the moment.

// scripts per transaction being simultaneously deserialized by 125
// peers. Thus, the peak usage of the free list is 12,500 * 512 =
// 6,400,000 bytes.
freeListMaxItems = 12500
Copy link
Member

Choose a reason for hiding this comment

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

And of course if the feeListMaxScriptSize is tweaked, this and/or the comment would need to be updated too.

// that would otherwise be needed to keep track of millions of small
// allocations.
//
// NOTE: It is no longer valid to call the returnScriptBuffers closure
Copy link
Member

@davecgh davecgh Sep 23, 2016

Choose a reason for hiding this comment

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

This part of the comment should be on the function definition now since it was split out and there is no longer any such function accessible in this scope.

Also, since it's not in scope even at the function level, the comment should be modified accordingly. Perhaps something like NOTE: It is no longer valid to return any previously borrowed script buffers after ...

// writeTxInScriptsToMsgTx allocates the memory for variable length fields in a
// MsgTx TxIns as a contiguous chunk of memory, then fills in these fields for the
// MsgTx by copying to a contiguous piece of memory and setting the pointer.
func writeTxInScriptsToMsgTx(msg *MsgTx, totalScriptSize uint64) {
Copy link
Member

@davecgh davecgh Sep 23, 2016

Choose a reason for hiding this comment

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

Splitting these reduces the effectiveness since it will end up with two allocations instead of one single allocation. I would create a single func like writeTxScriptsToMsgTx or even makeScriptsContiguous(msg *MsgTx, totalScriptSize uint64)

switch {
case mType == TxSerializeNoWitness:
err := msg.decodePrefix(r, pver)
err := msg.decodePrefix(r, pver, returnScriptBuffers)
Copy link
Member

@davecgh davecgh Sep 23, 2016

Choose a reason for hiding this comment

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

Rather than passing the returnScriptBuffers func to all of these and calling it in their error paths, I propose just calling it from this function in the if err != nil { blocks. That way the called funcs can't forget to invoke it since they aren't responsible for it.

As a case in point, the decodeWitnessSigning function never returns the buffers on error.

@@ -701,11 +898,13 @@ func (msg *MsgTx) decodeWitness(r io.Reader, pver uint32, isFull bool) error {
}
}

writeTxInScriptsToMsgTx(msg, totalScriptSize)
Copy link
Member

@davecgh davecgh Sep 23, 2016

Choose a reason for hiding this comment

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

I think this needs a comment to explain what's going on since this would be quite unintuitive for a reader that is not already familiar with this code.

}
msg.TxOut = make([]*TxOut, 0)

writeTxOutScriptsToMsgTx(msg, totalScriptSize)
Copy link
Member

Choose a reason for hiding this comment

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

I think this needs a comment to explain what's going on since this would be quite unintuitive for a reader that is not already familiar with this code.

return err
}

writeTxOutScriptsToMsgTx(msg, totalScriptSize)
Copy link
Member

Choose a reason for hiding this comment

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

I think this needs a comment to explain what's going on since this would be quite unintuitive for a reader that is not already familiar with this code.

return err
}

writeTxInScriptsToMsgTx(msg, totalScriptSize)
Copy link
Member

Choose a reason for hiding this comment

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

I think these need a comment to explain what's going on since this would be quite unintuitive for a reader that is not already familiar with this code.

@cjepson
Copy link
Contributor Author

cjepson commented Sep 24, 2016

Cleaned up a lot. The writes have been merged into one, and the closure is no longer passed through the functions. Should be easier to read.

Copy link
Member

@davecgh davecgh left a comment

Choose a reason for hiding this comment

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

The new code to make generate and use the contiguous buffer is much cleaner and easier to follow after the updates and all issues have been addressed. Nice job.

OK

@cjepson cjepson merged commit 615c018 into decred:master Sep 24, 2016
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.

2 participants