mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-02-26 07:37:20 +00:00
core/rawdb: reduce allocations in rawdb.ReadHeaderNumber (#31913)
This is something interesting I came across during my benchmarks, we
spent ~3.8% of all allocations allocating the header number on the heap.
```
(pprof) list GetHeaderByHash
Total: 38197204475
ROUTINE ======================== github.com/ethereum/go-ethereum/core.(*BlockChain).GetHeaderByHash in github.com/ethereum/go-ethereum/core/blockchain_reader.go
0 5786566117 (flat, cum) 15.15% of Total
. . 79:func (bc *BlockChain) GetHeaderByHash(hash common.Hash) *types.Header {
. 5786566117 80: return bc.hc.GetHeaderByHash(hash)
. . 81:}
. . 82:
. . 83:// GetHeaderByNumber retrieves a block header from the database by number,
. . 84:// caching it (associated with its hash) if found.
. . 85:func (bc *BlockChain) GetHeaderByNumber(number uint64) *types.Header {
ROUTINE ======================== github.com/ethereum/go-ethereum/core.(*HeaderChain).GetHeaderByHash in github.com/ethereum/go-ethereum/core/headerchain.go
0 5786566117 (flat, cum) 15.15% of Total
. . 404:func (hc *HeaderChain) GetHeaderByHash(hash common.Hash) *types.Header {
. 1471264309 405: number := hc.GetBlockNumber(hash)
. . 406: if number == nil {
. . 407: return nil
. . 408: }
. 4315301808 409: return hc.GetHeader(hash, *number)
. . 410:}
. . 411:
. . 412:// HasHeader checks if a block header is present in the database or not.
. . 413:// In theory, if header is present in the database, all relative components
. . 414:// like td and hash->number should be present too.
(pprof) list GetBlockNumber
Total: 38197204475
ROUTINE ======================== github.com/ethereum/go-ethereum/core.(*HeaderChain).GetBlockNumber in github.com/ethereum/go-ethereum/core/headerchain.go
94438817 1471264309 (flat, cum) 3.85% of Total
. . 100:func (hc *HeaderChain) GetBlockNumber(hash common.Hash) *uint64 {
94438817 94438817 101: if cached, ok := hc.numberCache.Get(hash); ok {
. . 102: return &cached
. . 103: }
. 1376270828 104: number := rawdb.ReadHeaderNumber(hc.chainDb, hash)
. . 105: if number != nil {
. 554664 106: hc.numberCache.Add(hash, *number)
. . 107: }
. . 108: return number
. . 109:}
. . 110:
. . 111:type headerWriteResult struct {
(pprof) list ReadHeaderNumber
Total: 38197204475
ROUTINE ======================== github.com/ethereum/go-ethereum/core/rawdb.ReadHeaderNumber in github.com/ethereum/go-ethereum/core/rawdb/accessors_chain.go
204606513 1376270828 (flat, cum) 3.60% of Total
. . 146:func ReadHeaderNumber(db ethdb.KeyValueReader, hash common.Hash) *uint64 {
109577863 1281242178 147: data, _ := db.Get(headerNumberKey(hash))
. . 148: if len(data) != 8 {
. . 149: return nil
. . 150: }
95028650 95028650 151: number := binary.BigEndian.Uint64(data)
. . 152: return &number
. . 153:}
. . 154:
. . 155:// WriteHeaderNumber stores the hash->number mapping.
. . 156:func WriteHeaderNumber(db ethdb.KeyValueWriter, hash common.Hash, number uint64) {
```
Opening this to discuss the idea, I know that rawdb.EmptyNumber is not a
great name for the variable, open to suggestions
This commit is contained in:
parent
7fcb796f64
commit
e94123acc2
10 changed files with 79 additions and 67 deletions
|
|
@ -576,8 +576,8 @@ func parseDumpConfig(ctx *cli.Context, db ethdb.Database) (*state.DumpConfig, co
|
|||
arg := ctx.Args().First()
|
||||
if hashish(arg) {
|
||||
hash := common.HexToHash(arg)
|
||||
if number := rawdb.ReadHeaderNumber(db, hash); number != nil {
|
||||
header = rawdb.ReadHeader(db, hash, *number)
|
||||
if number, ok := rawdb.ReadHeaderNumber(db, hash); ok {
|
||||
header = rawdb.ReadHeader(db, hash, number)
|
||||
} else {
|
||||
return nil, common.Hash{}, fmt.Errorf("block %x not found", hash)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,7 +91,10 @@ func (bc *BlockChain) GetHeaderByNumber(number uint64) *types.Header {
|
|||
|
||||
// GetBlockNumber retrieves the block number associated with a block hash.
|
||||
func (bc *BlockChain) GetBlockNumber(hash common.Hash) *uint64 {
|
||||
return bc.hc.GetBlockNumber(hash)
|
||||
if num, ok := bc.hc.GetBlockNumber(hash); ok {
|
||||
return &num
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetHeadersFrom returns a contiguous segment of headers, in rlp-form, going
|
||||
|
|
@ -107,11 +110,11 @@ func (bc *BlockChain) GetBody(hash common.Hash) *types.Body {
|
|||
if cached, ok := bc.bodyCache.Get(hash); ok {
|
||||
return cached
|
||||
}
|
||||
number := bc.hc.GetBlockNumber(hash)
|
||||
if number == nil {
|
||||
number, ok := bc.hc.GetBlockNumber(hash)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
body := rawdb.ReadBody(bc.db, hash, *number)
|
||||
body := rawdb.ReadBody(bc.db, hash, number)
|
||||
if body == nil {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -127,11 +130,11 @@ func (bc *BlockChain) GetBodyRLP(hash common.Hash) rlp.RawValue {
|
|||
if cached, ok := bc.bodyRLPCache.Get(hash); ok {
|
||||
return cached
|
||||
}
|
||||
number := bc.hc.GetBlockNumber(hash)
|
||||
if number == nil {
|
||||
number, ok := bc.hc.GetBlockNumber(hash)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
body := rawdb.ReadBodyRLP(bc.db, hash, *number)
|
||||
body := rawdb.ReadBodyRLP(bc.db, hash, number)
|
||||
if len(body) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -180,11 +183,11 @@ func (bc *BlockChain) GetBlock(hash common.Hash, number uint64) *types.Block {
|
|||
|
||||
// GetBlockByHash retrieves a block from the database by hash, caching it if found.
|
||||
func (bc *BlockChain) GetBlockByHash(hash common.Hash) *types.Block {
|
||||
number := bc.hc.GetBlockNumber(hash)
|
||||
if number == nil {
|
||||
number, ok := bc.hc.GetBlockNumber(hash)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return bc.GetBlock(hash, *number)
|
||||
return bc.GetBlock(hash, number)
|
||||
}
|
||||
|
||||
// GetBlockByNumber retrieves a block from the database by number, caching it
|
||||
|
|
@ -200,18 +203,18 @@ func (bc *BlockChain) GetBlockByNumber(number uint64) *types.Block {
|
|||
// GetBlocksFromHash returns the block corresponding to hash and up to n-1 ancestors.
|
||||
// [deprecated by eth/62]
|
||||
func (bc *BlockChain) GetBlocksFromHash(hash common.Hash, n int) (blocks []*types.Block) {
|
||||
number := bc.hc.GetBlockNumber(hash)
|
||||
if number == nil {
|
||||
number, ok := bc.hc.GetBlockNumber(hash)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
block := bc.GetBlock(hash, *number)
|
||||
block := bc.GetBlock(hash, number)
|
||||
if block == nil {
|
||||
break
|
||||
}
|
||||
blocks = append(blocks, block)
|
||||
hash = block.ParentHash()
|
||||
*number--
|
||||
number--
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -259,15 +262,15 @@ func (bc *BlockChain) GetReceiptsByHash(hash common.Hash) types.Receipts {
|
|||
if receipts, ok := bc.receiptsCache.Get(hash); ok {
|
||||
return receipts
|
||||
}
|
||||
number := rawdb.ReadHeaderNumber(bc.db, hash)
|
||||
if number == nil {
|
||||
number, ok := rawdb.ReadHeaderNumber(bc.db, hash)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
header := bc.GetHeader(hash, *number)
|
||||
header := bc.GetHeader(hash, number)
|
||||
if header == nil {
|
||||
return nil
|
||||
}
|
||||
receipts := rawdb.ReadReceipts(bc.db, hash, *number, header.Time, bc.chainConfig)
|
||||
receipts := rawdb.ReadReceipts(bc.db, hash, number, header.Time, bc.chainConfig)
|
||||
if receipts == nil {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -286,11 +289,11 @@ func (bc *BlockChain) GetRawReceipts(hash common.Hash, number uint64) types.Rece
|
|||
|
||||
// GetReceiptsRLP retrieves the receipts of a block.
|
||||
func (bc *BlockChain) GetReceiptsRLP(hash common.Hash) rlp.RawValue {
|
||||
number := rawdb.ReadHeaderNumber(bc.db, hash)
|
||||
if number == nil {
|
||||
number, ok := rawdb.ReadHeaderNumber(bc.db, hash)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return rawdb.ReadReceiptsRLP(bc.db, hash, *number)
|
||||
return rawdb.ReadReceiptsRLP(bc.db, hash, number)
|
||||
}
|
||||
|
||||
// GetUnclesInChain retrieves all the uncles from a given block backwards until
|
||||
|
|
|
|||
|
|
@ -782,13 +782,13 @@ func testFastVsFullChains(t *testing.T, scheme string) {
|
|||
}
|
||||
|
||||
// Check that hash-to-number mappings are present in all databases.
|
||||
if m := rawdb.ReadHeaderNumber(fastDb, hash); m == nil || *m != num {
|
||||
if m, ok := rawdb.ReadHeaderNumber(fastDb, hash); !ok || m != num {
|
||||
t.Errorf("block #%d [%x]: wrong hash-to-number mapping in fastdb: %v", num, hash, m)
|
||||
}
|
||||
if m := rawdb.ReadHeaderNumber(ancientDb, hash); m == nil || *m != num {
|
||||
if m, ok := rawdb.ReadHeaderNumber(ancientDb, hash); !ok || m != num {
|
||||
t.Errorf("block #%d [%x]: wrong hash-to-number mapping in ancientdb: %v", num, hash, m)
|
||||
}
|
||||
if m := rawdb.ReadHeaderNumber(archiveDb, hash); m == nil || *m != num {
|
||||
if m, ok := rawdb.ReadHeaderNumber(archiveDb, hash); !ok || m != num {
|
||||
t.Errorf("block #%d [%x]: wrong hash-to-number mapping in archivedb: %v", num, hash, m)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,15 +97,15 @@ func NewHeaderChain(chainDb ethdb.Database, config *params.ChainConfig, engine c
|
|||
|
||||
// GetBlockNumber retrieves the block number belonging to the given hash
|
||||
// from the cache or database
|
||||
func (hc *HeaderChain) GetBlockNumber(hash common.Hash) *uint64 {
|
||||
func (hc *HeaderChain) GetBlockNumber(hash common.Hash) (uint64, bool) {
|
||||
if cached, ok := hc.numberCache.Get(hash); ok {
|
||||
return &cached
|
||||
return cached, true
|
||||
}
|
||||
number := rawdb.ReadHeaderNumber(hc.chainDb, hash)
|
||||
if number != nil {
|
||||
hc.numberCache.Add(hash, *number)
|
||||
number, ok := rawdb.ReadHeaderNumber(hc.chainDb, hash)
|
||||
if ok {
|
||||
hc.numberCache.Add(hash, number)
|
||||
}
|
||||
return number
|
||||
return number, ok
|
||||
}
|
||||
|
||||
type headerWriteResult struct {
|
||||
|
|
@ -402,11 +402,11 @@ func (hc *HeaderChain) GetHeader(hash common.Hash, number uint64) *types.Header
|
|||
// GetHeaderByHash retrieves a block header from the database by hash, caching it if
|
||||
// found.
|
||||
func (hc *HeaderChain) GetHeaderByHash(hash common.Hash) *types.Header {
|
||||
number := hc.GetBlockNumber(hash)
|
||||
if number == nil {
|
||||
number, ok := hc.GetBlockNumber(hash)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return hc.GetHeader(hash, *number)
|
||||
return hc.GetHeader(hash, number)
|
||||
}
|
||||
|
||||
// HasHeader checks if a block header is present in the database or not.
|
||||
|
|
|
|||
|
|
@ -143,13 +143,13 @@ func ReadAllCanonicalHashes(db ethdb.Iteratee, from uint64, to uint64, limit int
|
|||
}
|
||||
|
||||
// ReadHeaderNumber returns the header number assigned to a hash.
|
||||
func ReadHeaderNumber(db ethdb.KeyValueReader, hash common.Hash) *uint64 {
|
||||
func ReadHeaderNumber(db ethdb.KeyValueReader, hash common.Hash) (uint64, bool) {
|
||||
data, _ := db.Get(headerNumberKey(hash))
|
||||
if len(data) != 8 {
|
||||
return nil
|
||||
return 0, false
|
||||
}
|
||||
number := binary.BigEndian.Uint64(data)
|
||||
return &number
|
||||
return number, true
|
||||
}
|
||||
|
||||
// WriteHeaderNumber stores the hash->number mapping.
|
||||
|
|
@ -935,11 +935,11 @@ func ReadHeadHeader(db ethdb.Reader) *types.Header {
|
|||
if headHeaderHash == (common.Hash{}) {
|
||||
return nil
|
||||
}
|
||||
headHeaderNumber := ReadHeaderNumber(db, headHeaderHash)
|
||||
if headHeaderNumber == nil {
|
||||
headHeaderNumber, ok := ReadHeaderNumber(db, headHeaderHash)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return ReadHeader(db, headHeaderHash, *headHeaderNumber)
|
||||
return ReadHeader(db, headHeaderHash, headHeaderNumber)
|
||||
}
|
||||
|
||||
// ReadHeadBlock returns the current canonical head block.
|
||||
|
|
@ -948,9 +948,9 @@ func ReadHeadBlock(db ethdb.Reader) *types.Block {
|
|||
if headBlockHash == (common.Hash{}) {
|
||||
return nil
|
||||
}
|
||||
headBlockNumber := ReadHeaderNumber(db, headBlockHash)
|
||||
if headBlockNumber == nil {
|
||||
headBlockNumber, ok := ReadHeaderNumber(db, headBlockHash)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return ReadBlock(db, headBlockHash, *headBlockNumber)
|
||||
return ReadBlock(db, headBlockHash, headBlockNumber)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,11 @@ func DecodeTxLookupEntry(data []byte, db ethdb.Reader) *uint64 {
|
|||
}
|
||||
// Database v4-v5 tx lookup format just stores the hash
|
||||
if len(data) == common.HashLength {
|
||||
return ReadHeaderNumber(db, common.BytesToHash(data))
|
||||
number, ok := ReadHeaderNumber(db, common.BytesToHash(data))
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return &number
|
||||
}
|
||||
// Finally try database v3 tx lookup format
|
||||
var entry LegacyTxLookupEntry
|
||||
|
|
|
|||
|
|
@ -107,12 +107,12 @@ func (f *chainFreezer) readHeadNumber(db ethdb.KeyValueReader) uint64 {
|
|||
log.Error("Head block is not reachable")
|
||||
return 0
|
||||
}
|
||||
number := ReadHeaderNumber(db, hash)
|
||||
if number == nil {
|
||||
number, ok := ReadHeaderNumber(db, hash)
|
||||
if !ok {
|
||||
log.Error("Number of head block is missing")
|
||||
return 0
|
||||
}
|
||||
return *number
|
||||
return number
|
||||
}
|
||||
|
||||
// readFinalizedNumber returns the number of finalized block. 0 is returned
|
||||
|
|
@ -122,12 +122,12 @@ func (f *chainFreezer) readFinalizedNumber(db ethdb.KeyValueReader) uint64 {
|
|||
if hash == (common.Hash{}) {
|
||||
return 0
|
||||
}
|
||||
number := ReadHeaderNumber(db, hash)
|
||||
if number == nil {
|
||||
number, ok := ReadHeaderNumber(db, hash)
|
||||
if !ok {
|
||||
log.Error("Number of finalized block is missing")
|
||||
return 0
|
||||
}
|
||||
return *number
|
||||
return number
|
||||
}
|
||||
|
||||
// freezeThreshold returns the threshold for chain freezing. It's determined
|
||||
|
|
|
|||
|
|
@ -268,7 +268,12 @@ func Open(db ethdb.KeyValueStore, opts OpenOptions) (ethdb.Database, error) {
|
|||
if kvhash, _ := db.Get(headerHashKey(frozen)); len(kvhash) == 0 {
|
||||
// Subsequent header after the freezer limit is missing from the database.
|
||||
// Reject startup if the database has a more recent head.
|
||||
if head := *ReadHeaderNumber(db, ReadHeadHeaderHash(db)); head > frozen-1 {
|
||||
head, ok := ReadHeaderNumber(db, ReadHeadHeaderHash(db))
|
||||
if !ok {
|
||||
printChainMetadata(db)
|
||||
return nil, fmt.Errorf("could not read header number, hash %v", ReadHeadHeaderHash(db))
|
||||
}
|
||||
if head > frozen-1 {
|
||||
// Find the smallest block stored in the key-value store
|
||||
// in range of [frozen, head]
|
||||
var number uint64
|
||||
|
|
|
|||
|
|
@ -217,11 +217,11 @@ func (indexer *txIndexer) resolveHead() uint64 {
|
|||
if headBlockHash == (common.Hash{}) {
|
||||
return 0
|
||||
}
|
||||
headBlockNumber := rawdb.ReadHeaderNumber(indexer.db, headBlockHash)
|
||||
if headBlockNumber == nil {
|
||||
headBlockNumber, ok := rawdb.ReadHeaderNumber(indexer.db, headBlockHash)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return *headBlockNumber
|
||||
return headBlockNumber
|
||||
}
|
||||
|
||||
// loop is the scheduler of the indexer, assigning indexing/unindexing tasks depending
|
||||
|
|
|
|||
|
|
@ -92,18 +92,18 @@ func (b *testBackend) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumbe
|
|||
switch blockNr {
|
||||
case rpc.LatestBlockNumber:
|
||||
hash = rawdb.ReadHeadBlockHash(b.db)
|
||||
number := rawdb.ReadHeaderNumber(b.db, hash)
|
||||
if number == nil {
|
||||
number, ok := rawdb.ReadHeaderNumber(b.db, hash)
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
num = *number
|
||||
num = number
|
||||
case rpc.FinalizedBlockNumber:
|
||||
hash = rawdb.ReadFinalizedBlockHash(b.db)
|
||||
number := rawdb.ReadHeaderNumber(b.db, hash)
|
||||
if number == nil {
|
||||
number, ok := rawdb.ReadHeaderNumber(b.db, hash)
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
num = *number
|
||||
num = number
|
||||
case rpc.SafeBlockNumber:
|
||||
return nil, errors.New("safe block not found")
|
||||
default:
|
||||
|
|
@ -114,11 +114,11 @@ func (b *testBackend) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumbe
|
|||
}
|
||||
|
||||
func (b *testBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
|
||||
number := rawdb.ReadHeaderNumber(b.db, hash)
|
||||
if number == nil {
|
||||
number, ok := rawdb.ReadHeaderNumber(b.db, hash)
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
return rawdb.ReadHeader(b.db, hash, *number), nil
|
||||
return rawdb.ReadHeader(b.db, hash, number), nil
|
||||
}
|
||||
|
||||
func (b *testBackend) GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error) {
|
||||
|
|
@ -129,9 +129,9 @@ func (b *testBackend) GetBody(ctx context.Context, hash common.Hash, number rpc.
|
|||
}
|
||||
|
||||
func (b *testBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
|
||||
if number := rawdb.ReadHeaderNumber(b.db, hash); number != nil {
|
||||
if header := rawdb.ReadHeader(b.db, hash, *number); header != nil {
|
||||
return rawdb.ReadReceipts(b.db, hash, *number, header.Time, params.TestChainConfig), nil
|
||||
if number, ok := rawdb.ReadHeaderNumber(b.db, hash); ok {
|
||||
if header := rawdb.ReadHeader(b.db, hash, number); header != nil {
|
||||
return rawdb.ReadReceipts(b.db, hash, number, header.Time, params.TestChainConfig), nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
|
|
|
|||
Loading…
Reference in a new issue