From 0fcdcb950ac7f37a6102ef33b7d13cf95703bb8b Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 19 Oct 2016 22:49:31 +0200 Subject: [PATCH 1/4] TEMP HACK to return memory to leveldb buffer pool --- .../_workspace/src/github.com/syndtr/goleveldb/leveldb/db.go | 4 ++++ ethdb/database.go | 4 ++++ ethdb/interface.go | 2 ++ ethdb/memory_database.go | 3 +++ trie/trie.go | 4 ++++ 5 files changed, 17 insertions(+) diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db.go index eb6abd0fb1..47bbff51bc 100644 --- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db.go +++ b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db.go @@ -1017,6 +1017,10 @@ func (db *DB) SizeOf(ranges []util.Range) (Sizes, error) { return sizes, nil } +func (db *DB) ReturnBuffer(b []byte) { + db.s.tops.bpool.Put(b) +} + // Close closes the DB. This will also releases any outstanding snapshot, // abort any in-flight compaction and discard open transaction. // diff --git a/ethdb/database.go b/ethdb/database.go index 479c54b607..80d77b4f0b 100644 --- a/ethdb/database.go +++ b/ethdb/database.go @@ -142,6 +142,10 @@ func (self *LDBDatabase) Get(key []byte) ([]byte, error) { //return rle.Decompress(dat) } +func (self *LDBDatabase) ReturnBuffer(b []byte) { + self.db.ReturnBuffer(b) +} + // Delete deletes the key from the queue and database func (self *LDBDatabase) Delete(key []byte) error { // Measure the database delete latency, if requested diff --git a/ethdb/interface.go b/ethdb/interface.go index f4b787a52a..cb39ecd959 100644 --- a/ethdb/interface.go +++ b/ethdb/interface.go @@ -22,6 +22,8 @@ type Database interface { Delete(key []byte) error Close() NewBatch() Batch + + ReturnBuffer([]byte) } type Batch interface { diff --git a/ethdb/memory_database.go b/ethdb/memory_database.go index a729f52332..27680ef0e4 100644 --- a/ethdb/memory_database.go +++ b/ethdb/memory_database.go @@ -62,6 +62,9 @@ func (db *MemDatabase) Get(key []byte) ([]byte, error) { return nil, errors.New("not found") } +func (db *MemDatabase) ReturnBuffer(b []byte) { +} + func (db *MemDatabase) Keys() [][]byte { db.lock.RLock() defer db.lock.RUnlock() diff --git a/trie/trie.go b/trie/trie.go index 035a80e74c..e637674e65 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -23,6 +23,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto/sha3" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" "github.com/rcrowley/go-metrics" @@ -453,6 +454,9 @@ func (t *Trie) resolveHash(n hashNode, prefix, suffix []byte) (node, error) { } } dec := mustDecodeNode(n, enc, t.cachegen) + if edb, ok := t.db.(*ethdb.LDBDatabase); ok { + edb.ReturnBuffer(enc) + } return dec, nil } From 1a49362b273dc887a59583f743d5eb576def6057 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 19 Oct 2016 22:48:21 +0200 Subject: [PATCH 2/4] trie: bump cachegen for read access, avoiding copies This change ensures that the cache generation of trie branches is bumped for reads. To avoid allocating new nodes for each read, the accessed node is modified directly if it was created in the current cache generation. --- trie/hasher.go | 2 ++ trie/node.go | 5 +++-- trie/trie.go | 44 +++++++++++++++++++++++++------------------- 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/trie/hasher.go b/trie/hasher.go index e6261819c1..bb446444ab 100644 --- a/trie/hasher.go +++ b/trie/hasher.go @@ -85,11 +85,13 @@ func (h *hasher) hash(n node, db DatabaseWriter, force bool) (node, node, error) cn.flags.hash = cachedHash if db != nil { cn.flags.dirty = false + cn.flags.new = false } case *fullNode: cn.flags.hash = cachedHash if db != nil { cn.flags.dirty = false + cn.flags.new = false } } return hashed, cached, nil diff --git a/trie/node.go b/trie/node.go index 4aa0cab65a..09c4a23da2 100644 --- a/trie/node.go +++ b/trie/node.go @@ -60,6 +60,7 @@ type nodeFlag struct { hash hashNode // cached hash of the node (may be nil) gen uint16 // cache generation counter dirty bool // whether the node has changes that must be written to the database + new bool // whether the node was allocated in the current generation } // canUnload tells whether a node can be unloaded. @@ -138,7 +139,7 @@ func decodeShort(hash, buf, elems []byte, cachegen uint16) (node, error) { if err != nil { return nil, err } - flag := nodeFlag{hash: hash, gen: cachegen} + flag := nodeFlag{hash: hash, gen: cachegen, new: true} key := compactDecode(kbuf) if key[len(key)-1] == 16 { // value node @@ -156,7 +157,7 @@ func decodeShort(hash, buf, elems []byte, cachegen uint16) (node, error) { } func decodeFull(hash, buf, elems []byte, cachegen uint16) (*fullNode, error) { - n := &fullNode{flags: nodeFlag{hash: hash, gen: cachegen}} + n := &fullNode{flags: nodeFlag{hash: hash, gen: cachegen, new: true}} for i := 0; i < 16; i++ { cld, rest, err := decodeRef(elems, cachegen) if err != nil { diff --git a/trie/trie.go b/trie/trie.go index e637674e65..6c0abde8c0 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -99,7 +99,7 @@ func (t *Trie) SetCacheLimit(l uint16) { // newFlag returns the cache flag value for a newly created node. func (t *Trie) newFlag() nodeFlag { - return nodeFlag{dirty: true, gen: t.cachegen} + return nodeFlag{new: true, dirty: true, gen: t.cachegen} } // New creates a trie with an existing root node from db. @@ -143,46 +143,52 @@ func (t *Trie) Get(key []byte) []byte { // If a node was not found in the database, a MissingNodeError is returned. func (t *Trie) TryGet(key []byte) ([]byte, error) { key = compactHexDecode(key) - value, newroot, didResolve, err := t.tryGet(t.root, key, 0) - if err == nil && didResolve { + value, newroot, err := t.tryGet(t.root, key, 0) + if err == nil { t.root = newroot } return value, err } -func (t *Trie) tryGet(origNode node, key []byte, pos int) (value []byte, newnode node, didResolve bool, err error) { +func (t *Trie) tryGet(origNode node, key []byte, pos int) (value []byte, newnode node, err error) { switch n := (origNode).(type) { case nil: - return nil, nil, false, nil + return nil, nil, nil case valueNode: - return n, n, false, nil + return n, n, nil case *shortNode: if len(key)-pos < len(n.Key) || !bytes.Equal(n.Key, key[pos:pos+len(n.Key)]) { // key not found in trie - return nil, n, false, nil + return nil, n, nil } - value, newnode, didResolve, err = t.tryGet(n.Val, key, pos+len(n.Key)) - if err == nil && didResolve { - n = n.copy() - n.Val = newnode + value, newnode, err = t.tryGet(n.Val, key, pos+len(n.Key)) + if err == nil { + if !n.flags.new { + n = n.copy() + n.flags.new = true + } n.flags.gen = t.cachegen + n.Val = newnode } - return value, n, didResolve, err + return value, n, err case *fullNode: - value, newnode, didResolve, err = t.tryGet(n.Children[key[pos]], key, pos+1) - if err == nil && didResolve { - n = n.copy() + value, newnode, err = t.tryGet(n.Children[key[pos]], key, pos+1) + if err == nil { + if !n.flags.new { + n = n.copy() + n.flags.new = true + } n.flags.gen = t.cachegen n.Children[key[pos]] = newnode } - return value, n, didResolve, err + return value, n, err case hashNode: child, err := t.resolveHash(n, key[:pos], key[pos:]) if err != nil { - return nil, n, true, err + return nil, n, err } - value, newnode, _, err := t.tryGet(child, key, pos) - return value, newnode, true, err + value, newnode, err := t.tryGet(child, key, pos) + return value, newnode, err default: panic(fmt.Sprintf("%T: invalid node: %v", origNode, origNode)) } From c937a70143ed6caeed7decdf418cc37dc028ef62 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 20 Oct 2016 00:20:10 +0200 Subject: [PATCH 3/4] trie: fix test --- trie/trie_test.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/trie/trie_test.go b/trie/trie_test.go index 14ac5a6669..b4761605c9 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -329,8 +329,7 @@ func TestCacheUnload(t *testing.T) { root, _ := trie.Commit() // Commit the trie repeatedly and access key1. - // The branch containing it is loaded from DB exactly two times: - // in the 0th and 6th iteration. + // The branch containing it is loaded from DB exactly once. db := &countingDB{Database: trie.db, gets: make(map[string]int)} trie, _ = New(root, db) trie.SetCacheLimit(5) @@ -339,10 +338,10 @@ func TestCacheUnload(t *testing.T) { trie.Commit() } - // Check that it got loaded two times. + // Check that it got loaded once. for dbkey, count := range db.gets { - if count != 2 { - t.Errorf("db key %x loaded %d times, want %d times", []byte(dbkey), count, 2) + if count != 1 { + t.Errorf("db key %x loaded %d times, want %d times", []byte(dbkey), count, 1) } } } From ddf63d3c068f1af50a2ef3ccde2e1551e4bef6b9 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 20 Oct 2016 00:49:58 +0200 Subject: [PATCH 4/4] trie: simplify insert, delete --- trie/trie.go | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/trie/trie.go b/trie/trie.go index 6c0abde8c0..61ca911f23 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -290,11 +290,7 @@ func (t *Trie) insert(n node, prefix, key []byte, value node) (bool, node, error if err != nil { return false, nil, err } - dirty, nn, err := t.insert(rn, prefix, key, value) - if !dirty || err != nil { - return false, rn, err - } - return true, nn, nil + return t.insert(rn, prefix, key, value) default: panic(fmt.Sprintf("%T: invalid node: %v", n, n)) @@ -421,11 +417,7 @@ func (t *Trie) delete(n node, prefix, key []byte) (bool, node, error) { if err != nil { return false, nil, err } - dirty, nn, err := t.delete(rn, prefix, key) - if !dirty || err != nil { - return false, rn, err - } - return true, nn, nil + return t.delete(rn, prefix, key) default: panic(fmt.Sprintf("%T: invalid node: %v (%v)", n, n, key))