Vastly optimize the MRU inventory handling.
Profiling showed the MRU inventory handling was taking 5% of the total block handling time. Upon inspection this is because the original implementation was extremely inefficient using iteration to find the oldest node for eviction. This commit reworks it to use a map and list in order to achieve close to O(1) performance for lookups, insertion, and eviction. The following benchmark results show the difference: Before: BenchmarkMruInventoryList 100 1069168 ns/op After: BenchmarkMruInventoryList 10000000 152 ns/op Closes #21
This commit is contained in:
parent
1fadf619c7
commit
261e61f8ee
1 changed files with 35 additions and 21 deletions
50
mruinvmap.go
50
mruinvmap.go
|
@ -5,15 +5,16 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"container/list"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/conformal/btcwire"
|
"github.com/conformal/btcwire"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// MruInventoryMap provides a map that is limited to a maximum number of items
|
// MruInventoryMap provides a map that is limited to a maximum number of items
|
||||||
// with eviction for the oldest entry when the limit is exceeded.
|
// with eviction for the oldest entry when the limit is exceeded.
|
||||||
type MruInventoryMap struct {
|
type MruInventoryMap struct {
|
||||||
invMap map[btcwire.InvVect]int64 // Use int64 for time for less mem.
|
invMap map[btcwire.InvVect]*list.Element // nearly O(1) lookups
|
||||||
|
invList *list.List // O(1) insert, update, delete
|
||||||
limit uint
|
limit uint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,40 +35,52 @@ func (m *MruInventoryMap) Exists(iv *btcwire.InvVect) bool {
|
||||||
// item if adding the new item would exceed the max limit.
|
// item if adding the new item would exceed the max limit.
|
||||||
func (m *MruInventoryMap) Add(iv *btcwire.InvVect) {
|
func (m *MruInventoryMap) Add(iv *btcwire.InvVect) {
|
||||||
// When the limit is zero, nothing can be added to the map, so just
|
// When the limit is zero, nothing can be added to the map, so just
|
||||||
// return
|
// return.
|
||||||
if m.limit == 0 {
|
if m.limit == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// When the entry already exists update its last seen time.
|
// When the entry already exists move it to the front of the list
|
||||||
if m.Exists(iv) {
|
// thereby marking it most recently used.
|
||||||
m.invMap[*iv] = time.Now().Unix()
|
if node, exists := m.invMap[*iv]; exists {
|
||||||
|
m.invList.MoveToFront(node)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evict the oldest entry if the the new entry would exceed the size
|
// Evict the least recently used entry (back of the list) if the the new
|
||||||
// limit for the map.
|
// entry would exceed the size limit for the map. Also reuse the list
|
||||||
|
// node so a new one doesn't have to be allocated.
|
||||||
if uint(len(m.invMap))+1 > m.limit {
|
if uint(len(m.invMap))+1 > m.limit {
|
||||||
var oldestEntry btcwire.InvVect
|
node := m.invList.Back()
|
||||||
var oldestTime int64
|
lru, ok := node.Value.(*btcwire.InvVect)
|
||||||
for iv, lastUpdated := range m.invMap {
|
if !ok {
|
||||||
if oldestTime == 0 || lastUpdated < oldestTime {
|
return
|
||||||
oldestEntry = iv
|
|
||||||
oldestTime = lastUpdated
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m.Delete(&oldestEntry)
|
// Evict least recently used item.
|
||||||
|
delete(m.invMap, *lru)
|
||||||
|
|
||||||
|
// Reuse the list node of the item that was just evicted for the
|
||||||
|
// new item.
|
||||||
|
node.Value = iv
|
||||||
|
m.invList.MoveToFront(node)
|
||||||
|
m.invMap[*iv] = node
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
m.invMap[*iv] = time.Now().Unix()
|
// The limit hasn't been reached yet, so just add the new item.
|
||||||
|
node := m.invList.PushFront(iv)
|
||||||
|
m.invMap[*iv] = node
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete deletes the passed inventory item from the map (if it exists).
|
// Delete deletes the passed inventory item from the map (if it exists).
|
||||||
func (m *MruInventoryMap) Delete(iv *btcwire.InvVect) {
|
func (m *MruInventoryMap) Delete(iv *btcwire.InvVect) {
|
||||||
|
if node, exists := m.invMap[*iv]; exists {
|
||||||
|
m.invList.Remove(node)
|
||||||
delete(m.invMap, *iv)
|
delete(m.invMap, *iv)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NewMruInventoryMap returns a new inventory map that is limited to the number
|
// NewMruInventoryMap returns a new inventory map that is limited to the number
|
||||||
// of entries specified by limit. When the number of entries exceeds the limit,
|
// of entries specified by limit. When the number of entries exceeds the limit,
|
||||||
|
@ -75,7 +88,8 @@ func (m *MruInventoryMap) Delete(iv *btcwire.InvVect) {
|
||||||
// new entry..
|
// new entry..
|
||||||
func NewMruInventoryMap(limit uint) *MruInventoryMap {
|
func NewMruInventoryMap(limit uint) *MruInventoryMap {
|
||||||
m := MruInventoryMap{
|
m := MruInventoryMap{
|
||||||
invMap: make(map[btcwire.InvVect]int64),
|
invMap: make(map[btcwire.InvVect]*list.Element),
|
||||||
|
invList: list.New(),
|
||||||
limit: limit,
|
limit: limit,
|
||||||
}
|
}
|
||||||
return &m
|
return &m
|
||||||
|
|
Loading…
Reference in a new issue