-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #57 from ElrondNetwork/lru-cache-from-elrond-go
copied LRU cache from elrond-go
- Loading branch information
Showing
10 changed files
with
1,505 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package storage | ||
|
||
import ( | ||
"errors" | ||
) | ||
|
||
// ErrCacheSizeInvalid signals that size of cache is less than 1 | ||
var ErrCacheSizeInvalid = errors.New("cache size is less than 1") | ||
|
||
// ErrCacheCapacityInvalid signals that capacity of cache is less than 1 | ||
var ErrCacheCapacityInvalid = errors.New("cache capacity is less than 1") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package storage | ||
|
||
// Cacher provides caching services | ||
type Cacher interface { | ||
// Clear is used to completely clear the cache. | ||
Clear() | ||
// Put adds a value to the cache. Returns true if an eviction occurred. | ||
Put(key []byte, value interface{}, sizeInBytes int) (evicted bool) | ||
// Get looks up a key's value from the cache. | ||
Get(key []byte) (value interface{}, ok bool) | ||
// Has checks if a key is in the cache, without updating the | ||
// recent-ness or deleting it for being stale. | ||
Has(key []byte) bool | ||
// Peek returns the key value (or undefined if not found) without updating | ||
// the "recently used"-ness of the key. | ||
Peek(key []byte) (value interface{}, ok bool) | ||
// HasOrAdd checks if a key is in the cache without updating the | ||
// recent-ness or deleting it for being stale, and if not adds the value. | ||
HasOrAdd(key []byte, value interface{}, sizeInBytes int) (has, added bool) | ||
// Remove removes the provided key from the cache. | ||
Remove(key []byte) | ||
// Keys returns a slice of the keys in the cache, from oldest to newest. | ||
Keys() [][]byte | ||
// Len returns the number of items in the cache. | ||
Len() int | ||
// SizeInBytesContained returns the size in bytes of all contained elements | ||
SizeInBytesContained() uint64 | ||
// MaxSize returns the maximum number of items which can be stored in the cache. | ||
MaxSize() int | ||
// RegisterHandler registers a new handler to be called when a new data is added | ||
RegisterHandler(handler func(key []byte, value interface{}), id string) | ||
// UnRegisterHandler deletes the handler from the list | ||
UnRegisterHandler(id string) | ||
// Close closes the underlying temporary db if the cacher implementation has one, | ||
// otherwise it does nothing | ||
Close() error | ||
// IsInterfaceNil returns true if there is no value under the interface | ||
IsInterfaceNil() bool | ||
} | ||
|
||
// ForEachItem is an iterator callback | ||
type ForEachItem func(key []byte, value interface{}) | ||
|
||
// LRUCacheHandler is the interface for LRU cache. | ||
type LRUCacheHandler interface { | ||
Add(key, value interface{}) bool | ||
Get(key interface{}) (value interface{}, ok bool) | ||
Contains(key interface{}) (ok bool) | ||
ContainsOrAdd(key, value interface{}) (ok, evicted bool) | ||
Peek(key interface{}) (value interface{}, ok bool) | ||
Remove(key interface{}) bool | ||
Keys() []interface{} | ||
Len() int | ||
Purge() | ||
} | ||
|
||
// SizedLRUCacheHandler is the interface for size capable LRU cache. | ||
type SizedLRUCacheHandler interface { | ||
AddSized(key, value interface{}, sizeInBytes int64) bool | ||
Get(key interface{}) (value interface{}, ok bool) | ||
Contains(key interface{}) (ok bool) | ||
AddSizedIfMissing(key, value interface{}, sizeInBytes int64) (ok, evicted bool) | ||
Peek(key interface{}) (value interface{}, ok bool) | ||
Remove(key interface{}) bool | ||
Keys() []interface{} | ||
Len() int | ||
SizeInBytesContained() uint64 | ||
Purge() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,284 @@ | ||
package capacity | ||
|
||
import ( | ||
"container/list" | ||
"sync" | ||
|
||
"github.com/ElrondNetwork/elrond-go-core/storage" | ||
) | ||
|
||
// capacityLRU implements a non thread safe LRU Cache with a max capacity size | ||
type capacityLRU struct { | ||
lock sync.Mutex | ||
size int | ||
maxCapacityInBytes int64 | ||
currentCapacityInBytes int64 | ||
//TODO investigate if we can replace this list with a binary tree. Check also the other implementation lruCache | ||
evictList *list.List | ||
items map[interface{}]*list.Element | ||
} | ||
|
||
// entry is used to hold a value in the evictList | ||
type entry struct { | ||
key interface{} | ||
value interface{} | ||
size int64 | ||
} | ||
|
||
// NewCapacityLRU constructs an CapacityLRU of the given size with a byte size capacity | ||
func NewCapacityLRU(size int, byteCapacity int64) (*capacityLRU, error) { | ||
if size < 1 { | ||
return nil, storage.ErrCacheSizeInvalid | ||
} | ||
if byteCapacity < 1 { | ||
return nil, storage.ErrCacheCapacityInvalid | ||
} | ||
c := &capacityLRU{ | ||
size: size, | ||
maxCapacityInBytes: byteCapacity, | ||
evictList: list.New(), | ||
items: make(map[interface{}]*list.Element), | ||
} | ||
return c, nil | ||
} | ||
|
||
// Purge is used to completely clear the cache. | ||
func (c *capacityLRU) Purge() { | ||
c.lock.Lock() | ||
defer c.lock.Unlock() | ||
|
||
c.items = make(map[interface{}]*list.Element) | ||
c.evictList.Init() | ||
c.currentCapacityInBytes = 0 | ||
} | ||
|
||
// AddSized adds a value to the cache. Returns true if an eviction occurred. | ||
func (c *capacityLRU) AddSized(key, value interface{}, sizeInBytes int64) bool { | ||
c.lock.Lock() | ||
defer c.lock.Unlock() | ||
|
||
c.addSized(key, value, sizeInBytes) | ||
|
||
return c.evictIfNeeded() | ||
} | ||
|
||
func (c *capacityLRU) addSized(key interface{}, value interface{}, sizeInBytes int64) { | ||
if sizeInBytes < 0 { | ||
return | ||
} | ||
|
||
// Check for existing item | ||
if ent, ok := c.items[key]; ok { | ||
c.update(key, value, sizeInBytes, ent) | ||
} else { | ||
c.addNew(key, value, sizeInBytes) | ||
} | ||
} | ||
|
||
// AddSizedAndReturnEvicted adds the given key-value pair to the cache, and returns the evicted values | ||
func (c *capacityLRU) AddSizedAndReturnEvicted(key, value interface{}, sizeInBytes int64) map[interface{}]interface{} { | ||
c.lock.Lock() | ||
defer c.lock.Unlock() | ||
|
||
c.addSized(key, value, sizeInBytes) | ||
|
||
evictedValues := make(map[interface{}]interface{}) | ||
for c.shouldEvict() { | ||
evicted := c.evictList.Back() | ||
if evicted == nil { | ||
continue | ||
} | ||
|
||
c.removeElement(evicted) | ||
evictedEntry, ok := evicted.Value.(*entry) | ||
if !ok { | ||
continue | ||
} | ||
|
||
evictedValues[evictedEntry.key] = evictedEntry.value | ||
} | ||
|
||
return evictedValues | ||
} | ||
|
||
func (c *capacityLRU) addNew(key interface{}, value interface{}, sizeInBytes int64) { | ||
ent := &entry{ | ||
key: key, | ||
value: value, | ||
size: sizeInBytes, | ||
} | ||
e := c.evictList.PushFront(ent) | ||
c.items[key] = e | ||
c.currentCapacityInBytes += sizeInBytes | ||
} | ||
|
||
func (c *capacityLRU) update(key interface{}, value interface{}, sizeInBytes int64, ent *list.Element) { | ||
c.evictList.MoveToFront(ent) | ||
|
||
e := ent.Value.(*entry) | ||
sizeDiff := sizeInBytes - e.size | ||
e.value = value | ||
e.size = sizeInBytes | ||
c.currentCapacityInBytes += sizeDiff | ||
|
||
c.adjustSize(key, sizeInBytes) | ||
} | ||
|
||
// Get looks up a key's value from the cache. | ||
func (c *capacityLRU) Get(key interface{}) (interface{}, bool) { | ||
c.lock.Lock() | ||
defer c.lock.Unlock() | ||
|
||
if ent, ok := c.items[key]; ok { | ||
c.evictList.MoveToFront(ent) | ||
if ent.Value.(*entry) == nil { | ||
return nil, false | ||
} | ||
|
||
return ent.Value.(*entry).value, true | ||
} | ||
|
||
return nil, false | ||
} | ||
|
||
// Contains checks if a key is in the cache, without updating the recent-ness | ||
// or deleting it for being stale. | ||
func (c *capacityLRU) Contains(key interface{}) bool { | ||
c.lock.Lock() | ||
defer c.lock.Unlock() | ||
|
||
_, ok := c.items[key] | ||
|
||
return ok | ||
} | ||
|
||
// AddSizedIfMissing checks if a key is in the cache without updating the | ||
// recent-ness or deleting it for being stale, and if not, adds the value. | ||
// Returns whether found and whether an eviction occurred. | ||
func (c *capacityLRU) AddSizedIfMissing(key, value interface{}, sizeInBytes int64) (bool, bool) { | ||
if sizeInBytes < 0 { | ||
return false, false | ||
} | ||
|
||
c.lock.Lock() | ||
defer c.lock.Unlock() | ||
|
||
_, ok := c.items[key] | ||
if ok { | ||
return true, false | ||
} | ||
c.addNew(key, value, sizeInBytes) | ||
evicted := c.evictIfNeeded() | ||
|
||
return false, evicted | ||
} | ||
|
||
// Peek returns the key value (or undefined if not found) without updating | ||
// the "recently used"-ness of the key. | ||
func (c *capacityLRU) Peek(key interface{}) (interface{}, bool) { | ||
c.lock.Lock() | ||
defer c.lock.Unlock() | ||
|
||
ent, ok := c.items[key] | ||
if ok { | ||
return ent.Value.(*entry).value, true | ||
} | ||
return nil, ok | ||
} | ||
|
||
// Remove removes the provided key from the cache, returning if the | ||
// key was contained. | ||
func (c *capacityLRU) Remove(key interface{}) bool { | ||
c.lock.Lock() | ||
defer c.lock.Unlock() | ||
|
||
if ent, ok := c.items[key]; ok { | ||
c.removeElement(ent) | ||
return true | ||
} | ||
return false | ||
} | ||
|
||
// Keys returns a slice of the keys in the cache, from oldest to newest. | ||
func (c *capacityLRU) Keys() []interface{} { | ||
c.lock.Lock() | ||
defer c.lock.Unlock() | ||
|
||
keys := make([]interface{}, len(c.items)) | ||
i := 0 | ||
for ent := c.evictList.Back(); ent != nil; ent = ent.Prev() { | ||
keys[i] = ent.Value.(*entry).key | ||
i++ | ||
} | ||
return keys | ||
} | ||
|
||
// Len returns the number of items in the cache. | ||
func (c *capacityLRU) Len() int { | ||
c.lock.Lock() | ||
defer c.lock.Unlock() | ||
|
||
return c.evictList.Len() | ||
} | ||
|
||
// SizeInBytesContained returns the size in bytes of all contained elements | ||
func (c *capacityLRU) SizeInBytesContained() uint64 { | ||
c.lock.Lock() | ||
defer c.lock.Unlock() | ||
|
||
return uint64(c.currentCapacityInBytes) | ||
} | ||
|
||
// removeOldest removes the oldest item from the cache. | ||
func (c *capacityLRU) removeOldest() { | ||
ent := c.evictList.Back() | ||
if ent != nil { | ||
c.removeElement(ent) | ||
} | ||
} | ||
|
||
// removeElement is used to remove a given list element from the cache | ||
func (c *capacityLRU) removeElement(e *list.Element) { | ||
c.evictList.Remove(e) | ||
kv := e.Value.(*entry) | ||
delete(c.items, kv.key) | ||
c.currentCapacityInBytes -= kv.size | ||
} | ||
|
||
func (c *capacityLRU) adjustSize(key interface{}, sizeInBytes int64) { | ||
element := c.items[key] | ||
if element == nil || element.Value == nil || element.Value.(*entry) == nil { | ||
return | ||
} | ||
|
||
v := element.Value.(*entry) | ||
c.currentCapacityInBytes -= v.size | ||
v.size = sizeInBytes | ||
element.Value = v | ||
c.currentCapacityInBytes += sizeInBytes | ||
c.evictIfNeeded() | ||
} | ||
|
||
func (c *capacityLRU) shouldEvict() bool { | ||
if c.evictList.Len() == 1 { | ||
// keep at least one element, no matter how large it is | ||
return false | ||
} | ||
|
||
return c.evictList.Len() > c.size || c.currentCapacityInBytes > c.maxCapacityInBytes | ||
} | ||
|
||
func (c *capacityLRU) evictIfNeeded() bool { | ||
evicted := false | ||
for c.shouldEvict() { | ||
c.removeOldest() | ||
evicted = true | ||
} | ||
|
||
return evicted | ||
} | ||
|
||
// IsInterfaceNil returns true if there is no value under the interface | ||
func (c *capacityLRU) IsInterfaceNil() bool { | ||
return c == nil | ||
} |
Oops, something went wrong.