From f5cdf2d6a85dff81f2aeba8b2a5ef557f8c0aba5 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Sat, 4 Apr 2015 13:25:49 -0500 Subject: [PATCH] Minor hashing-related optimizations. This commit contains three classes of optimizations: - Reducing the number of unnecessary hash copies - Improve the performance of the DoubleSha256 function - A couple of minor optimizations of the ShaHash functions The first class is a result of the Bytes function on a ShaHash making a copy of the bytes before returning them. It really should have been named CloneBytes, but that would break the API now. To address this, a comment has been added to the function which explicitly calls out the copy behavior. In addition, all call sites of .Bytes on a ShaHash in the code base have been updated to simply slice the array when a copy is not needed. This saves a significant amount of data copying. The second optimization modifies the DoubleSha256 function to directly use fastsha256.Sum256 instead of the hasher interface. This reduces the number of allocations needed. A benchmark for the function has been added as well. old: BenchmarkDoubleSha256 500000 3691 ns/op 192 B/op 3 allocs/op new: BenchmarkDoubleSha256 500000 3081 ns/op 32 B/op 1 allocs/op The final optimizations are for the ShaHash IsEqual and SetBytes functions which have been modified to make use of the fact the type is an array and remove an unneeded subslice. --- blockchain/difficulty.go | 7 +++---- blockchain/merkle.go | 4 ++-- database/ldb/block.go | 7 +++---- database/ldb/leveldb.go | 17 +++++++++-------- database/ldb/tx.go | 2 +- server.go | 7 ++++--- wire/bench_test.go | 17 +++++++++++++++++ wire/common.go | 9 +++------ wire/shahash.go | 9 ++++++--- 9 files changed, 48 insertions(+), 31 deletions(-) diff --git a/blockchain/difficulty.go b/blockchain/difficulty.go index b9662195..b0f5f195 100644 --- a/blockchain/difficulty.go +++ b/blockchain/difficulty.go @@ -57,15 +57,14 @@ var ( // perform math comparisons. func ShaHashToBig(hash *wire.ShaHash) *big.Int { // A ShaHash is in little-endian, but the big package wants the bytes - // in big-endian. Reverse them. ShaHash.Bytes makes a copy, so it - // is safe to modify the returned buffer. - buf := hash.Bytes() + // in big-endian, so reverse them. + buf := *hash blen := len(buf) for i := 0; i < blen/2; i++ { buf[i], buf[blen-1-i] = buf[blen-1-i], buf[i] } - return new(big.Int).SetBytes(buf) + return new(big.Int).SetBytes(buf[:]) } // CompactToBig converts a compact representation of a whole number N to an diff --git a/blockchain/merkle.go b/blockchain/merkle.go index 13c901dd..5d7e6367 100644 --- a/blockchain/merkle.go +++ b/blockchain/merkle.go @@ -31,8 +31,8 @@ func nextPowerOfTwo(n int) int { func HashMerkleBranches(left *wire.ShaHash, right *wire.ShaHash) *wire.ShaHash { // Concatenate the left and right nodes. var sha [wire.HashSize * 2]byte - copy(sha[:wire.HashSize], left.Bytes()) - copy(sha[wire.HashSize:], right.Bytes()) + copy(sha[:wire.HashSize], left[:]) + copy(sha[wire.HashSize:], right[:]) // Create a new sha hash from the double sha 256. Ignore the error // here since SetBytes can't fail here due to the fact DoubleSha256 diff --git a/database/ldb/block.go b/database/ldb/block.go index 7e2f8ff2..5533f69a 100644 --- a/database/ldb/block.go +++ b/database/ldb/block.go @@ -134,10 +134,9 @@ func (db *LevelDb) setBlk(sha *wire.ShaHash, blkHeight int64, buf []byte) { shaKey := shaBlkToKey(sha) blkKey := int64ToKey(blkHeight) - shaB := sha.Bytes() - blkVal := make([]byte, len(shaB)+len(buf)) - copy(blkVal[0:], shaB) - copy(blkVal[len(shaB):], buf) + blkVal := make([]byte, len(sha)+len(buf)) + copy(blkVal[0:], sha[:]) + copy(blkVal[len(sha):], buf) db.lBatch().Put(shaKey, lw[:]) db.lBatch().Put(blkKey, blkVal) diff --git a/database/ldb/leveldb.go b/database/ldb/leveldb.go index 64af1663..9a10f349 100644 --- a/database/ldb/leveldb.go +++ b/database/ldb/leveldb.go @@ -641,8 +641,7 @@ func int64ToKey(keyint int64) []byte { } func shaBlkToKey(sha *wire.ShaHash) []byte { - shaB := sha.Bytes() - return shaB + return sha[:] } // These are used here and in tx.go's deleteOldAddrIndex() to prevent deletion @@ -651,15 +650,17 @@ var recordSuffixTx = []byte{'t', 'x'} var recordSuffixSpentTx = []byte{'s', 'x'} func shaTxToKey(sha *wire.ShaHash) []byte { - shaB := sha.Bytes() - shaB = append(shaB, recordSuffixTx...) - return shaB + key := make([]byte, len(sha)+len(recordSuffixTx)) + copy(key, sha[:]) + copy(key[len(sha):], recordSuffixTx) + return key } func shaSpentTxToKey(sha *wire.ShaHash) []byte { - shaB := sha.Bytes() - shaB = append(shaB, recordSuffixSpentTx...) - return shaB + key := make([]byte, len(sha)+len(recordSuffixSpentTx)) + copy(key, sha[:]) + copy(key[len(sha):], recordSuffixSpentTx) + return key } func (db *LevelDb) lBatch() *leveldb.Batch { diff --git a/database/ldb/tx.go b/database/ldb/tx.go index 4f834e76..7fff041e 100644 --- a/database/ldb/tx.go +++ b/database/ldb/tx.go @@ -537,7 +537,7 @@ func (db *LevelDb) UpdateAddrIndexForBlock(blkSha *wire.ShaHash, blkHeight int64 // Update tip of addrindex. newIndexTip := make([]byte, 40, 40) - copy(newIndexTip[0:32], blkSha.Bytes()) + copy(newIndexTip[0:32], blkSha[:]) binary.LittleEndian.PutUint64(newIndexTip[32:40], uint64(blkHeight)) batch.Put(addrIndexMetaDataKey, newIndexTip) diff --git a/server.go b/server.go index a24a2817..9a17c60e 100644 --- a/server.go +++ b/server.go @@ -5,7 +5,6 @@ package main import ( - "bytes" "container/list" "crypto/rand" "encoding/binary" @@ -215,13 +214,15 @@ func (s *server) handleUpdatePeerHeights(state *peerState, umsg updatePeerHeight return } - latestBlkSha := p.lastAnnouncedBlock.Bytes() + // This is a pointer to the underlying memory which doesn't + // change. + latestBlkSha := p.lastAnnouncedBlock p.StatsMtx.Unlock() // If the peer has recently announced a block, and this block // matches our newly accepted block, then update their block // height. - if bytes.Equal(latestBlkSha, umsg.newSha.Bytes()) { + if *latestBlkSha == *umsg.newSha { p.UpdateLastBlockHeight(umsg.newHeight) p.UpdateLastAnnouncedBlock(nil) } diff --git a/wire/bench_test.go b/wire/bench_test.go index 3c0350ee..b7d2b7d5 100644 --- a/wire/bench_test.go +++ b/wire/bench_test.go @@ -392,3 +392,20 @@ func BenchmarkTxSha(b *testing.B) { genesisCoinbaseTx.TxSha() } } + +// BenchmarkDoubleSha256 performs a benchmark on how long it takes to perform a +// double sha 256. +func BenchmarkDoubleSha256(b *testing.B) { + b.StopTimer() + var buf bytes.Buffer + if err := genesisCoinbaseTx.Serialize(&buf); err != nil { + b.Errorf("Serialize: unexpected error: %v", err) + return + } + txBytes := buf.Bytes() + b.StartTimer() + + for i := 0; i < b.N; i++ { + _ = DoubleSha256(txBytes) + } +} diff --git a/wire/common.go b/wire/common.go index bf41d68e..27264951 100644 --- a/wire/common.go +++ b/wire/common.go @@ -522,10 +522,7 @@ func RandomUint64() (uint64, error) { // DoubleSha256 calculates sha256(sha256(b)) and returns the resulting bytes. func DoubleSha256(b []byte) []byte { - hasher := fastsha256.New() - hasher.Write(b) - sum := hasher.Sum(nil) - hasher.Reset() - hasher.Write(sum) - return hasher.Sum(nil) + first := fastsha256.Sum256(b) + second := fastsha256.Sum256(first[:]) + return second[:] } diff --git a/wire/shahash.go b/wire/shahash.go index 63f30ac0..41ca3f66 100644 --- a/wire/shahash.go +++ b/wire/shahash.go @@ -5,7 +5,6 @@ package wire import ( - "bytes" "encoding/hex" "fmt" ) @@ -34,6 +33,10 @@ func (hash ShaHash) String() string { } // Bytes returns the bytes which represent the hash as a byte slice. +// +// NOTE: This makes a copy of the bytes and should have probably been named +// CloneBytes. It is generally cheaper to just slice the hash directly thereby +// reusing the same bytes rather than calling this method. func (hash *ShaHash) Bytes() []byte { newHash := make([]byte, HashSize) copy(newHash, hash[:]) @@ -49,14 +52,14 @@ func (hash *ShaHash) SetBytes(newHash []byte) error { return fmt.Errorf("invalid sha length of %v, want %v", nhlen, HashSize) } - copy(hash[:], newHash[0:HashSize]) + copy(hash[:], newHash) return nil } // IsEqual returns true if target is the same as hash. func (hash *ShaHash) IsEqual(target *ShaHash) bool { - return bytes.Equal(hash[:], target[:]) + return *hash == *target } // NewShaHash returns a new ShaHash from a byte slice. An error is returned if