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

blockchain: Use slices when fetching utxos #1972

Merged
merged 2 commits into from
May 11, 2023

Conversation

kcalvinalvin
Copy link
Collaborator

Maps have a higher overhead than slices. As slices can be used instead of maps, we avoid the overhead of making a map.

This was one of the overheads I've noticed when profiling ibd for PR #1955

@coveralls
Copy link

coveralls commented Apr 28, 2023

Pull Request Test Coverage Report for Build 4894156905

  • 17 of 25 (68.0%) changed or added relevant lines in 2 files are covered.
  • 4 unchanged lines in 1 file lost coverage.
  • Overall coverage decreased (-0.009%) to 55.265%

Changes Missing Coverage Covered Lines Changed/Added Lines %
blockchain/utxoviewpoint.go 13 21 61.9%
Files with Coverage Reduction New Missed Lines %
peer/peer.go 4 73.2%
Totals Coverage Status
Change from base Build 4755006470: -0.009%
Covered Lines: 26649
Relevant Lines: 48220

💛 - Coveralls

@@ -517,7 +517,7 @@ func (view *UtxoViewpoint) fetchUtxosMain(db database.DB, outpoints map[wire.Out
// so other code can use the presence of an entry in the store as a way
// to unnecessarily avoid attempting to reload it from the database.
return db.View(func(dbTx database.Tx) error {
for outpoint := range outpoints {
for _, outpoint := range outpoints {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should we use index based access to the slice to avoid any Golang loop variable issues?

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 can change it to index based access.

Could you let me know what the Golang loop variable issues are? I'm not aware of these issues so it'd be good to know.

Copy link
Collaborator

Choose a reason for hiding this comment

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

The variable is defined per-loop, not per-iteration. So depending on how it is used, it can lead to unexpected side effects. See https://mohamed-abdel-mohaimen.medium.com/using-loop-iterator-variable-issues-in-golang-b76c17fb71b6 or similar articles.

@@ -626,23 +626,23 @@ func (b *BlockChain) FetchUtxoView(tx *btcutil.Tx) (*UtxoViewpoint, error) {
// Create a set of needed outputs based on those referenced by the
// inputs of the passed transaction and the outputs of the transaction
// itself.
neededSet := make(map[wire.OutPoint]struct{})
needed := make([]wire.OutPoint, 0, len(tx.MsgTx().TxIn))
Copy link
Collaborator

Choose a reason for hiding this comment

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

We're pre-allocating for the length of TxIn but then loop over TxOut.

@kcalvinalvin
Copy link
Collaborator Author

Addressed the reviews in the latest push

@kcalvinalvin
Copy link
Collaborator Author

Pushed an extra commit with benchmarks.

I'm seeing a ~25% performance improvement.

[N] calvin@bitcoin ~/b/g/u/g/s/g/b/b/blockchain (utxoview-use-slices)> go test -run=XXX -bench=BenchmarkUtxo
goos: linux
goarch: amd64
pkg: github.com/btcsuite/btcd/blockchain
cpu: AMD Ryzen 5 3600 6-Core Processor              
BenchmarkUtxoFetchMap-10        17066674                71.06 ns/op
BenchmarkUtxoFetchSlices-10     19618462                52.60 ns/op
PASS
ok      github.com/btcsuite/btcd/blockchain     2.384s

// Already loaded into the current view.
if _, ok := view.entries[outpoint]; ok {
continue
}

neededSet[outpoint] = struct{}{}
needed = append(needed, outpoint)
Copy link
Collaborator

@guggero guggero May 5, 2023

Choose a reason for hiding this comment

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

I think this is the classic example of where things go wrong with Golang loop variables. Should use index based access to the outpoint. See https://stackoverflow.com/questions/71437577/appending-inside-a-loop-repeats-the-last-value-in-golang

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Addressed with the latest push.

Maps have a higher overhead than slices.  As slices can be used instead
of maps, we avoid the overhead of making a map.
Benchmark added to compare the performance of a map vs a slice when
fetching utxos.  The benchmark shows roughly 25% performance improvement
when using slices instead of a map.
Copy link
Collaborator

@guggero guggero left a comment

Choose a reason for hiding this comment

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

Nice, LGTM 🎉

Copy link
Collaborator

@halseth halseth left a comment

Choose a reason for hiding this comment

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

Great change, LGTM 💯

neededSet := make(map[wire.OutPoint]struct{})
for outpoint := range outpoints {
needed := make([]wire.OutPoint, 0, len(outpoints))
for i := range outpoints {
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: could do for _, outpoint := range outpoints to minimize diff

Copy link
Collaborator

Choose a reason for hiding this comment

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

No, I explicitly asked to use the index based access, because the append() would otherwise not work as expected because of the Golang loop variable issue (see my resolved comments above).

Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think that is an issue in this case, since it's a non-ref struct being used?

That being said, better safe than sorry 😀

Copy link
Collaborator

Choose a reason for hiding this comment

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

Hmm, you're right, in this case it's not problematic. But we've been burned by this a couple of times in the past, so I basically only use the key/index based access. Also makes it easier to review if you know the loop bug can't bite you.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Agreed!

@guggero guggero merged commit 98e3c49 into btcsuite:master May 11, 2023
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.

4 participants