mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-18 18:02:24 +00:00
trie, core: rough idea to improve derivesha
This commit is contained in:
parent
aacb8e6f06
commit
0455e7a9c6
4 changed files with 115 additions and 22 deletions
|
|
@ -14,26 +14,26 @@
|
|||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package trie
|
||||
package types
|
||||
|
||||
// bytesPool is a pool for byteslices. It is safe for concurrent use.
|
||||
type bytesPool struct {
|
||||
// BytesPool is a pool for byteslices. It is safe for concurrent use.
|
||||
type BytesPool struct {
|
||||
c chan []byte
|
||||
w int
|
||||
}
|
||||
|
||||
// newBytesPool creates a new bytesPool. The sliceCap sets the capacity of
|
||||
// NewBytesPool creates a new BytesPool. The sliceCap sets the capacity of
|
||||
// newly allocated slices, and the nitems determines how many items the pool
|
||||
// will hold, at maximum.
|
||||
func newBytesPool(sliceCap, nitems int) *bytesPool {
|
||||
return &bytesPool{
|
||||
func NewBytesPool(sliceCap, nitems int) *BytesPool {
|
||||
return &BytesPool{
|
||||
c: make(chan []byte, nitems),
|
||||
w: sliceCap,
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns a slice. Safe for concurrent use.
|
||||
func (bp *bytesPool) Get() []byte {
|
||||
func (bp *BytesPool) Get() []byte {
|
||||
select {
|
||||
case b := <-bp.c:
|
||||
return b
|
||||
|
|
@ -44,7 +44,7 @@ func (bp *bytesPool) Get() []byte {
|
|||
|
||||
// Put returns a slice to the pool. Safe for concurrent use. This method
|
||||
// will ignore slices that are too small or too large (>3x the cap)
|
||||
func (bp *bytesPool) Put(b []byte) {
|
||||
func (bp *BytesPool) Put(b []byte) {
|
||||
if c := cap(b); c < bp.w || c > 3*bp.w {
|
||||
return
|
||||
}
|
||||
|
|
@ -84,6 +84,11 @@ type TrieHasher interface {
|
|||
Hash() common.Hash
|
||||
}
|
||||
|
||||
type RecyclingTrieHasher interface {
|
||||
TrieHasher
|
||||
Reclaim(*BytesPool)
|
||||
}
|
||||
|
||||
// DerivableList is the input to DeriveSha.
|
||||
// It is implemented by the 'Transactions' and 'Receipts' types.
|
||||
// This is internal, do not use these methods.
|
||||
|
|
@ -95,10 +100,7 @@ type DerivableList interface {
|
|||
func encodeForDerive(list DerivableList, i int, buf *bytes.Buffer) []byte {
|
||||
buf.Reset()
|
||||
list.EncodeIndex(i, buf)
|
||||
// It's really unfortunate that we need to perform this copy.
|
||||
// StackTrie holds onto the values until Hash is called, so the values
|
||||
// written to it must not alias.
|
||||
return common.CopyBytes(buf.Bytes())
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// DeriveSha creates the tree hashes of transactions, receipts, and withdrawals in a block header.
|
||||
|
|
@ -118,17 +120,84 @@ func DeriveSha(list DerivableList, hasher TrieHasher) common.Hash {
|
|||
for i := 1; i < list.Len() && i <= 0x7f; i++ {
|
||||
indexBuf = rlp.AppendUint64(indexBuf[:0], uint64(i))
|
||||
value := encodeForDerive(list, i, valueBuf)
|
||||
// It's really unfortunate that we need to perform this copy.
|
||||
// StackTrie holds onto the values until Hash is called, so the values
|
||||
// written to it must not alias.
|
||||
value = common.CopyBytes(value)
|
||||
hasher.Update(indexBuf, value)
|
||||
}
|
||||
if list.Len() > 0 {
|
||||
indexBuf = rlp.AppendUint64(indexBuf[:0], 0)
|
||||
value := encodeForDerive(list, 0, valueBuf)
|
||||
value = common.CopyBytes(value)
|
||||
|
||||
hasher.Update(indexBuf, value)
|
||||
}
|
||||
for i := 0x80; i < list.Len(); i++ {
|
||||
indexBuf = rlp.AppendUint64(indexBuf[:0], uint64(i))
|
||||
value := encodeForDerive(list, i, valueBuf)
|
||||
value = common.CopyBytes(value)
|
||||
|
||||
hasher.Update(indexBuf, value)
|
||||
}
|
||||
return hasher.Hash()
|
||||
}
|
||||
|
||||
func DeriveShaNG(list DerivableList, hasher RecyclingTrieHasher) common.Hash {
|
||||
hasher.Reset()
|
||||
|
||||
valueBuf := encodeBufferPool.Get().(*bytes.Buffer)
|
||||
defer encodeBufferPool.Put(valueBuf)
|
||||
|
||||
cap := 300
|
||||
vPool := NewBytesPool(cap, 40)
|
||||
hasher.Reclaim(vPool)
|
||||
|
||||
// StackTrie requires values to be inserted in increasing hash order, which is not the
|
||||
// order that `list` provides hashes in. This insertion sequence ensures that the
|
||||
// order is correct.
|
||||
//
|
||||
// The error returned by hasher is omitted because hasher will produce an incorrect
|
||||
// hash in case any error occurs.
|
||||
var indexBuf []byte
|
||||
for i := 1; i < list.Len() && i <= 0x7f; i++ {
|
||||
indexBuf = rlp.AppendUint64(indexBuf[:0], uint64(i))
|
||||
value := encodeForDerive(list, i, valueBuf)
|
||||
// It's really unfortunate that we need to perform this copy.
|
||||
// StackTrie holds onto the values until Hash is called, so the values
|
||||
// written to it must not alias
|
||||
vBuf := vPool.Get()
|
||||
if cap < len(value) {
|
||||
vBuf = common.CopyBytes(value)
|
||||
} else {
|
||||
vBuf = vBuf[:len(value)]
|
||||
copy(vBuf, value)
|
||||
}
|
||||
hasher.Update(indexBuf, vBuf)
|
||||
}
|
||||
if list.Len() > 0 {
|
||||
indexBuf = rlp.AppendUint64(indexBuf[:0], 0)
|
||||
value := encodeForDerive(list, 0, valueBuf)
|
||||
vBuf := vPool.Get()
|
||||
if cap < len(value) {
|
||||
vBuf = common.CopyBytes(value)
|
||||
} else {
|
||||
vBuf = vBuf[:len(value)]
|
||||
copy(vBuf, value)
|
||||
}
|
||||
hasher.Update(indexBuf, vBuf)
|
||||
}
|
||||
for i := 0x80; i < list.Len(); i++ {
|
||||
indexBuf = rlp.AppendUint64(indexBuf[:0], uint64(i))
|
||||
value := encodeForDerive(list, i, valueBuf)
|
||||
vBuf := vPool.Get()
|
||||
if cap < len(value) {
|
||||
vBuf = common.CopyBytes(value)
|
||||
} else {
|
||||
vBuf = vBuf[:len(value)]
|
||||
copy(vBuf, value)
|
||||
}
|
||||
hasher.Update(indexBuf, vBuf)
|
||||
}
|
||||
return hasher.Hash()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,13 +81,18 @@ func BenchmarkDeriveSha200(b *testing.B) {
|
|||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
var exp common.Hash
|
||||
var got common.Hash
|
||||
var exp = types.DeriveSha(txs, trie.NewEmpty(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil)))
|
||||
|
||||
var have common.Hash
|
||||
|
||||
b.Run("std_trie", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
exp = types.DeriveSha(txs, trie.NewEmpty(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil)))
|
||||
have = types.DeriveSha(txs, trie.NewEmpty(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil)))
|
||||
}
|
||||
if have != exp {
|
||||
b.Errorf("got %x exp %x", have, exp)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -95,12 +100,22 @@ func BenchmarkDeriveSha200(b *testing.B) {
|
|||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
got = types.DeriveSha(txs, trie.NewStackTrie(nil))
|
||||
have = types.DeriveShaNG(txs, trie.NewStackTrie(nil))
|
||||
}
|
||||
if have != exp {
|
||||
b.Errorf("got %x exp %x", have, exp)
|
||||
}
|
||||
})
|
||||
if got != exp {
|
||||
b.Errorf("got %x exp %x", got, exp)
|
||||
}
|
||||
//b.Run("stack_trie_reuse", func(b *testing.B) {
|
||||
// b.ResetTimer()
|
||||
// b.ReportAllocs()
|
||||
// for i := 0; i < b.N; i++ {
|
||||
// have = types.DeriveShaNG(txs, trie.NewStackTrie(nil))
|
||||
// }
|
||||
// if have != exp {
|
||||
// b.Errorf("got %x exp %x", have, exp)
|
||||
// }
|
||||
//})
|
||||
}
|
||||
|
||||
func TestFuzzDeriveSha(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import (
|
|||
|
||||
var (
|
||||
stPool = sync.Pool{New: func() any { return new(stNode) }}
|
||||
bPool = newBytesPool(32, 100)
|
||||
bPool = types.NewBytesPool(32, 100)
|
||||
_ = types.TrieHasher((*StackTrie)(nil))
|
||||
)
|
||||
|
||||
|
|
@ -50,6 +50,7 @@ type StackTrie struct {
|
|||
onTrieNode OnTrieNode
|
||||
kBuf []byte // buf space used for hex-key during insertions
|
||||
pBuf []byte // buf space used for path during insertions
|
||||
vPool *types.BytesPool
|
||||
}
|
||||
|
||||
// NewStackTrie allocates and initializes an empty trie. The committed nodes
|
||||
|
|
@ -64,13 +65,17 @@ func NewStackTrie(onTrieNode OnTrieNode) *StackTrie {
|
|||
}
|
||||
}
|
||||
|
||||
func (st *StackTrie) Reclaim(vPool *types.BytesPool) {
|
||||
st.vPool = vPool
|
||||
}
|
||||
|
||||
// Update inserts a (key, value) pair into the stack trie.
|
||||
func (t *StackTrie) Update(key, value []byte) error {
|
||||
if len(value) == 0 {
|
||||
return errors.New("trying to insert empty (deletion)")
|
||||
}
|
||||
var k []byte
|
||||
{ // Need to expand the 'key' into hex-form. We use the dedicated buf for that.
|
||||
{ // Need to expand the 'key' into hex-form. We use the dedicated buf for that.
|
||||
if cap(t.kBuf) < 2*len(key) { // realloc to ensure sufficient cap
|
||||
t.kBuf = make([]byte, 2*len(key), 2*len(key))
|
||||
}
|
||||
|
|
@ -392,7 +397,11 @@ func (t *StackTrie) hash(st *stNode, path []byte) {
|
|||
st.typ = hashedNode
|
||||
st.key = st.key[:0]
|
||||
|
||||
st.val = nil // Release reference to potentially externally held slice.
|
||||
// Release reference to (potentially externally held) value-slice.
|
||||
if len(st.val) > 0 && t.vPool != nil {
|
||||
t.vPool.Put(st.val)
|
||||
}
|
||||
st.val = nil
|
||||
|
||||
// Skip committing the non-root node if the size is smaller than 32 bytes
|
||||
// as tiny nodes are always embedded in their parent except root node.
|
||||
|
|
|
|||
Loading…
Reference in a new issue