Skip to content

Commit

Permalink
blockchain: Use new uint256 for work sums.
Browse files Browse the repository at this point in the history
Live profiling data of performing an initial sync shows that roughly 36%
of all in-use allocations are the result of the big integers used to
store the cumulative work for each block. Further, around 12% of the
entire CPU time is spent scaning the heap for garbage collection which
is a direct result of the total number of inuse allocations.  Therefore,
a reasonable expectation is that eliminating those heap objects should
produce a speedup of around 4-5%.

Consequently, this modifies the blockchain package to make use of the
much more efficient zero-alloc uint256s and associated work calculation
funcs in the new primitives package that is under development.

Profiling shows the result is about 100MiB less heap usage on average
and a reduction of about 5% to the initial sync time which is in line
with the expected result.
  • Loading branch information
davecgh committed May 30, 2022
1 parent 24eb474 commit 7fc95ce
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 57 deletions.
16 changes: 8 additions & 8 deletions internal/blockchain/blockindex.go
Expand Up @@ -8,15 +8,15 @@ package blockchain
import (
"bytes"
"encoding/binary"
"math/big"
"sort"
"sync"
"time"

"github.com/decred/dcrd/blockchain/stake/v5"
"github.com/decred/dcrd/blockchain/standalone/v2"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/database/v3"
"github.com/decred/dcrd/internal/staging/primitives"
"github.com/decred/dcrd/math/uint256"
"github.com/decred/dcrd/wire"
)

Expand Down Expand Up @@ -128,7 +128,7 @@ type blockNode struct {

// workSum is the total amount of work in the chain up to and including
// this node.
workSum *big.Int
workSum uint256.Uint256

// Some fields from block headers to aid in best chain selection and
// reconstructing headers from memory. These must be treated as
Expand Down Expand Up @@ -233,7 +233,7 @@ func calcSkipListHeight(height int64) int64 {
func initBlockNode(node *blockNode, blockHeader *wire.BlockHeader, parent *blockNode) {
*node = blockNode{
hash: blockHeader.BlockHash(),
workSum: standalone.CalcWork(blockHeader.Bits),
workSum: primitives.CalcWork(blockHeader.Bits),
height: int64(blockHeader.Height),
blockVersion: blockHeader.Version,
voteBits: blockHeader.VoteBits,
Expand All @@ -256,7 +256,7 @@ func initBlockNode(node *blockNode, blockHeader *wire.BlockHeader, parent *block
if parent != nil {
node.parent = parent
node.skipToAncestor = parent.Ancestor(calcSkipListHeight(node.height))
node.workSum = node.workSum.Add(parent.workSum, node.workSum)
node.workSum.Add(&parent.workSum)
}
}

Expand Down Expand Up @@ -448,7 +448,7 @@ func betterCandidate(a, b *blockNode) bool {
//
// Blocks with more cumulative work are better candidates for best chain
// selection.
if workCmp := a.workSum.Cmp(b.workSum); workCmp != 0 {
if workCmp := a.workSum.Cmp(&b.workSum); workCmp != 0 {
return workCmp > 0
}

Expand Down Expand Up @@ -1249,7 +1249,7 @@ func (bi *blockIndex) removeLessWorkCandidates(node *blockNode) {
// Remove all best chain candidates that have less work than the passed
// node.
for n := range bi.bestChainCandidates {
if n.workSum.Cmp(node.workSum) < 0 {
if n.workSum.Lt(&node.workSum) {
bi.removeBestChainCandidate(n)
}
}
Expand Down Expand Up @@ -1306,7 +1306,7 @@ func (bi *blockIndex) linkBlockData(node, tip *blockNode) []*blockNode {

// The block is now a candidate to potentially become the best chain if
// it has the same or more work than the current best chain tip.
if linkedNode.workSum.Cmp(tip.workSum) >= 0 {
if linkedNode.workSum.GtEq(&tip.workSum) {
bi.addBestChainCandidate(linkedNode)
}

Expand Down
46 changes: 23 additions & 23 deletions internal/blockchain/blockindex_test.go
Expand Up @@ -6,13 +6,13 @@ package blockchain

import (
"fmt"
"math/big"
"math/rand"
"reflect"
"testing"
"time"

"github.com/decred/dcrd/chaincfg/v3"
"github.com/decred/dcrd/math/uint256"
"github.com/decred/dcrd/wire"
)

Expand Down Expand Up @@ -406,13 +406,13 @@ func TestBetterCandidate(t *testing.T) {
name: "exactly equal, both data",
nodeA: &blockNode{
hash: *mustParseHash("0000000000000000000000000000000000000000000000000000000000000000"),
workSum: big.NewInt(2),
workSum: *new(uint256.Uint256).SetUint64(2),
status: statusDataStored,
receivedOrderID: 0,
},
nodeB: &blockNode{
hash: *mustParseHash("0000000000000000000000000000000000000000000000000000000000000000"),
workSum: big.NewInt(2),
workSum: *new(uint256.Uint256).SetUint64(2),
status: statusDataStored,
receivedOrderID: 0,
},
Expand All @@ -422,12 +422,12 @@ func TestBetterCandidate(t *testing.T) {
name: "exactly equal, no data",
nodeA: &blockNode{
hash: *mustParseHash("0000000000000000000000000000000000000000000000000000000000000000"),
workSum: big.NewInt(2),
workSum: *new(uint256.Uint256).SetUint64(2),
receivedOrderID: 0,
},
nodeB: &blockNode{
hash: *mustParseHash("0000000000000000000000000000000000000000000000000000000000000000"),
workSum: big.NewInt(2),
workSum: *new(uint256.Uint256).SetUint64(2),
receivedOrderID: 0,
},
wantCmp: 0,
Expand All @@ -436,12 +436,12 @@ func TestBetterCandidate(t *testing.T) {
name: "a has more cumulative work, same order, higher hash, b has data",
nodeA: &blockNode{
hash: *higherHash,
workSum: big.NewInt(4),
workSum: *new(uint256.Uint256).SetUint64(4),
receivedOrderID: 0,
},
nodeB: &blockNode{
hash: *lowerHash,
workSum: big.NewInt(2),
workSum: *new(uint256.Uint256).SetUint64(2),
status: statusDataStored,
receivedOrderID: 0,
},
Expand All @@ -451,13 +451,13 @@ func TestBetterCandidate(t *testing.T) {
name: "a has less cumulative work, same order, lower hash, a has data",
nodeA: &blockNode{
hash: *lowerHash,
workSum: big.NewInt(2),
workSum: *new(uint256.Uint256).SetUint64(2),
status: statusDataStored,
receivedOrderID: 0,
},
nodeB: &blockNode{
hash: *higherHash,
workSum: big.NewInt(4),
workSum: *new(uint256.Uint256).SetUint64(4),
receivedOrderID: 0,
},
wantCmp: -1,
Expand All @@ -466,12 +466,12 @@ func TestBetterCandidate(t *testing.T) {
name: "a has same cumulative work, same order, lower hash, b has data",
nodeA: &blockNode{
hash: *lowerHash,
workSum: big.NewInt(2),
workSum: *new(uint256.Uint256).SetUint64(2),
receivedOrderID: 0,
},
nodeB: &blockNode{
hash: *higherHash,
workSum: big.NewInt(2),
workSum: *new(uint256.Uint256).SetUint64(2),
status: statusDataStored,
receivedOrderID: 0,
},
Expand All @@ -481,13 +481,13 @@ func TestBetterCandidate(t *testing.T) {
name: "a has same cumulative work, same order, higher hash, a has data",
nodeA: &blockNode{
hash: *higherHash,
workSum: big.NewInt(2),
workSum: *new(uint256.Uint256).SetUint64(2),
status: statusDataStored,
receivedOrderID: 0,
},
nodeB: &blockNode{
hash: *lowerHash,
workSum: big.NewInt(2),
workSum: *new(uint256.Uint256).SetUint64(2),
receivedOrderID: 0,
},
wantCmp: 1,
Expand All @@ -496,13 +496,13 @@ func TestBetterCandidate(t *testing.T) {
name: "a has same cumulative work, higher order, lower hash, both data",
nodeA: &blockNode{
hash: *lowerHash,
workSum: big.NewInt(2),
workSum: *new(uint256.Uint256).SetUint64(2),
status: statusDataStored,
receivedOrderID: 1,
},
nodeB: &blockNode{
hash: *higherHash,
workSum: big.NewInt(2),
workSum: *new(uint256.Uint256).SetUint64(2),
status: statusDataStored,
receivedOrderID: 0,
},
Expand All @@ -512,13 +512,13 @@ func TestBetterCandidate(t *testing.T) {
name: "a has same cumulative work, lower order, lower hash, both data",
nodeA: &blockNode{
hash: *lowerHash,
workSum: big.NewInt(2),
workSum: *new(uint256.Uint256).SetUint64(2),
status: statusDataStored,
receivedOrderID: 1,
},
nodeB: &blockNode{
hash: *higherHash,
workSum: big.NewInt(2),
workSum: *new(uint256.Uint256).SetUint64(2),
status: statusDataStored,
receivedOrderID: 2,
},
Expand All @@ -528,12 +528,12 @@ func TestBetterCandidate(t *testing.T) {
name: "a has same cumulative work, same order, lower hash, no data",
nodeA: &blockNode{
hash: *lowerHash,
workSum: big.NewInt(2),
workSum: *new(uint256.Uint256).SetUint64(2),
receivedOrderID: 0,
},
nodeB: &blockNode{
hash: *higherHash,
workSum: big.NewInt(2),
workSum: *new(uint256.Uint256).SetUint64(2),
receivedOrderID: 0,
},
wantCmp: -1,
Expand All @@ -542,13 +542,13 @@ func TestBetterCandidate(t *testing.T) {
name: "a has same cumulative work, same order, lower hash, both data",
nodeA: &blockNode{
hash: *lowerHash,
workSum: big.NewInt(2),
workSum: *new(uint256.Uint256).SetUint64(2),
status: statusDataStored,
receivedOrderID: 0,
},
nodeB: &blockNode{
hash: *higherHash,
workSum: big.NewInt(2),
workSum: *new(uint256.Uint256).SetUint64(2),
status: statusDataStored,
receivedOrderID: 0,
},
Expand All @@ -558,13 +558,13 @@ func TestBetterCandidate(t *testing.T) {
name: "a has same cumulative work, same order, higher hash, both data",
nodeA: &blockNode{
hash: *higherHash,
workSum: big.NewInt(2),
workSum: *new(uint256.Uint256).SetUint64(2),
status: statusDataStored,
receivedOrderID: 0,
},
nodeB: &blockNode{
hash: *lowerHash,
workSum: big.NewInt(2),
workSum: *new(uint256.Uint256).SetUint64(2),
status: statusDataStored,
receivedOrderID: 0,
},
Expand Down
21 changes: 16 additions & 5 deletions internal/blockchain/chain.go
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/decred/dcrd/internal/blockchain/indexers"
"github.com/decred/dcrd/internal/blockchain/spendpruner"
"github.com/decred/dcrd/lru"
"github.com/decred/dcrd/math/uint256"
"github.com/decred/dcrd/txscript/v4"
"github.com/decred/dcrd/wire"
)
Expand Down Expand Up @@ -141,6 +142,7 @@ type BlockChain struct {
allowOldForks bool
expectedBlocksInTwoWeeks int64
deploymentVers map[string]uint32
minKnownWork *uint256.Uint256
db database.DB
dbInfo *databaseInfo
chainParams *chaincfg.Params
Expand Down Expand Up @@ -467,7 +469,7 @@ func (b *BlockChain) ChainWork(hash *chainhash.Hash) (*big.Int, error) {
return nil, unknownBlockError(hash)
}

return node.workSum, nil
return node.workSum.ToBig(), nil
}

// TipGeneration returns the entire generation of blocks stemming from the
Expand Down Expand Up @@ -710,7 +712,7 @@ func (b *BlockChain) connectBlock(node *blockNode, block, parent *dcrutil.Block,
// Atomically insert info into the database.
err = b.db.Update(func(dbTx database.Tx) error {
// Update best block state.
err := dbPutBestState(dbTx, state, node.workSum)
err := dbPutBestState(dbTx, state, &node.workSum)
if err != nil {
return err
}
Expand Down Expand Up @@ -922,7 +924,7 @@ func (b *BlockChain) disconnectBlock(node *blockNode, block, parent *dcrutil.Blo

err = b.db.Update(func(dbTx database.Tx) error {
// Update best block state.
err := dbPutBestState(dbTx, state, node.workSum)
err := dbPutBestState(dbTx, state, &node.workSum)
if err != nil {
return err
}
Expand Down Expand Up @@ -1593,8 +1595,7 @@ func (b *BlockChain) maybeUpdateIsCurrent(curBest *blockNode) {
if !b.isCurrentLatch {
// Not current if the latest best block has a cumulative work less than
// the minimum known work specified by the network parameters.
minKnownWork := b.chainParams.MinKnownChainWork
if minKnownWork != nil && curBest.workSum.Cmp(minKnownWork) < 0 {
if b.minKnownWork != nil && curBest.workSum.Lt(b.minKnownWork) {
return
}

Expand Down Expand Up @@ -2371,6 +2372,15 @@ func New(ctx context.Context, config *Config) (*BlockChain, error) {
return nil, err
}

// Convert the minimum known work to a uint256 when it it exists. Ideally,
// the chain params should be updated to use the new type, but that will be
// a major version bump, so a one-time conversion is a good tradeoff in the
// mean time.
var minKnownWork *uint256.Uint256
if params.MinKnownChainWork != nil {
minKnownWork = new(uint256.Uint256).SetBig(params.MinKnownChainWork)
}

// Either use the subsidy cache provided by the caller or create a new
// one when one was not provided.
subsidyCache := config.SubsidyCache
Expand All @@ -2396,6 +2406,7 @@ func New(ctx context.Context, config *Config) (*BlockChain, error) {
allowOldForks: allowOldForks,
expectedBlocksInTwoWeeks: expectedBlksInTwoWeeks,
deploymentVers: deploymentVers,
minKnownWork: minKnownWork,
db: config.DB,
chainParams: params,
timeSource: config.TimeSource,
Expand Down

0 comments on commit 7fc95ce

Please sign in to comment.