|
|
|
|
@@ -1,270 +0,0 @@
|
|
|
|
|
package vm
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"container/list"
|
|
|
|
|
"fmt"
|
|
|
|
|
"sync"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type cachedFrame struct {
|
|
|
|
|
Key uint // pointer to key
|
|
|
|
|
Value *Frame // pointer to value
|
|
|
|
|
Frequency uint // frequency of access
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type frequencyNode struct {
|
|
|
|
|
count uint // frequency count - never decreases
|
|
|
|
|
valuesList *list.List // valuesList contains pointer to the head of values linked list
|
|
|
|
|
inner *list.Element // actual content of the next element
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// creates a new frequency list node with the given count
|
|
|
|
|
func newFrequencyNode(count uint) *frequencyNode {
|
|
|
|
|
return &frequencyNode{
|
|
|
|
|
count: count,
|
|
|
|
|
valuesList: list.New(),
|
|
|
|
|
inner: nil,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type keyRefNode struct {
|
|
|
|
|
inner *list.Element // contains the actual value wrapped by a list element
|
|
|
|
|
parentFreqNode *list.Element // contains reference to the frequency node element
|
|
|
|
|
keyRef uint // contains pointer to the key
|
|
|
|
|
valueRef *Frame // value
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// creates a new KeyRef node which is used to represent the value in linked list
|
|
|
|
|
func newKeyRefNode(keyRef uint, valueRef *Frame, parent *list.Element) *keyRefNode {
|
|
|
|
|
return &keyRefNode{
|
|
|
|
|
inner: nil,
|
|
|
|
|
parentFreqNode: parent,
|
|
|
|
|
keyRef: keyRef,
|
|
|
|
|
valueRef: valueRef,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// FrameCache implements all the methods and data-structures required for a LFU cache for caching frames.
|
|
|
|
|
type FrameCache struct {
|
|
|
|
|
rwLock sync.RWMutex // rwLock is a read-write mutex which provides concurrent reads but exclusive writes
|
|
|
|
|
lookupTable map[uint]*keyRefNode // a hash table of <KeyType, *ValueType> for quick reference of values based on keys
|
|
|
|
|
frequencies *list.List // internal linked list that contains frequency mapping
|
|
|
|
|
maxSize uint // maxSize represents the maximum number of elements that can be in the cache before eviction
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MaxSize returns the maximum size of the cache at that point in time
|
|
|
|
|
func (lfu *FrameCache) MaxSize() uint {
|
|
|
|
|
lfu.rwLock.RLock()
|
|
|
|
|
defer lfu.rwLock.RUnlock()
|
|
|
|
|
|
|
|
|
|
return lfu.maxSize
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CurrentSize returns the number of elements in that cache
|
|
|
|
|
func (lfu *FrameCache) CurrentSize() uint {
|
|
|
|
|
lfu.rwLock.RLock()
|
|
|
|
|
defer lfu.rwLock.RUnlock()
|
|
|
|
|
|
|
|
|
|
return uint(len(lfu.lookupTable))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// IsFull checks if the LFU cache is full
|
|
|
|
|
func (lfu *FrameCache) IsFull() bool {
|
|
|
|
|
lfu.rwLock.RLock()
|
|
|
|
|
defer lfu.rwLock.RUnlock()
|
|
|
|
|
|
|
|
|
|
return uint(len(lfu.lookupTable)) == lfu.maxSize
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SetMaxSize updates the max size of the LFU cache
|
|
|
|
|
func (lfu *FrameCache) SetMaxSize(size uint) {
|
|
|
|
|
lfu.rwLock.Lock()
|
|
|
|
|
defer lfu.rwLock.Unlock()
|
|
|
|
|
|
|
|
|
|
lfu.maxSize = size
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// evict the least recently used element from the cache, this function is unsafe to be called externally
|
|
|
|
|
// because it doesn't provide locking mechanism.
|
|
|
|
|
func (lfu *FrameCache) unsafeEvict() error {
|
|
|
|
|
// WARNING: This function assumes that a write lock has been held by the caller already
|
|
|
|
|
|
|
|
|
|
// get the head node of the list
|
|
|
|
|
headFreq := lfu.frequencies.Front()
|
|
|
|
|
if headFreq == nil {
|
|
|
|
|
// list is empty, this is a very unusual condition
|
|
|
|
|
return fmt.Errorf("internal error: failed to evict, empty frequency list")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
headFreqInner := (headFreq.Value).(*frequencyNode)
|
|
|
|
|
|
|
|
|
|
if headFreqInner.valuesList.Len() == 0 {
|
|
|
|
|
// again this is a very unusual condition
|
|
|
|
|
return fmt.Errorf("internal error: failed to evict, empty values list")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
headValuesList := headFreqInner.valuesList
|
|
|
|
|
// pop the head of this this values list
|
|
|
|
|
headValueNode := headValuesList.Front()
|
|
|
|
|
removeResult := headValuesList.Remove(headValueNode).(*keyRefNode)
|
|
|
|
|
|
|
|
|
|
// update the values list
|
|
|
|
|
headFreqInner.valuesList = headValuesList
|
|
|
|
|
|
|
|
|
|
if headFreqInner.valuesList.Len() == 0 && headFreqInner.count > 1 {
|
|
|
|
|
// this node can be removed from the frequency list
|
|
|
|
|
freqList := lfu.frequencies
|
|
|
|
|
freqList.Remove(headFreq)
|
|
|
|
|
lfu.frequencies = freqList
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// remove the key from lookup table
|
|
|
|
|
key := removeResult.keyRef
|
|
|
|
|
delete(lfu.lookupTable, key)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Put method inserts a `<KeyType, ValueType>` to the LFU cache and updates internal
|
|
|
|
|
// data structures to keep track of access frequencies, if the cache is full, it evicts the
|
|
|
|
|
// least frequently used value from the cache.
|
|
|
|
|
func (lfu *FrameCache) Put(key uint, value *Frame) error {
|
|
|
|
|
// get write lock
|
|
|
|
|
lfu.rwLock.Lock()
|
|
|
|
|
defer lfu.rwLock.Unlock()
|
|
|
|
|
|
|
|
|
|
if _, ok := lfu.lookupTable[key]; ok {
|
|
|
|
|
// update the cache value
|
|
|
|
|
lfu.lookupTable[key].valueRef = value
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if lfu.maxSize == uint(len(lfu.lookupTable)) {
|
|
|
|
|
lfu.unsafeEvict()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
valueNode := newKeyRefNode(key, value, nil)
|
|
|
|
|
|
|
|
|
|
head := lfu.frequencies.Front()
|
|
|
|
|
if head == nil {
|
|
|
|
|
// fresh linked list
|
|
|
|
|
freqNode := newFrequencyNode(1)
|
|
|
|
|
head = lfu.frequencies.PushFront(freqNode)
|
|
|
|
|
freqNode.inner = head
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
node := head.Value.(*frequencyNode)
|
|
|
|
|
if node.count != 1 {
|
|
|
|
|
freqNode := newFrequencyNode(1)
|
|
|
|
|
head = lfu.frequencies.PushFront(freqNode)
|
|
|
|
|
freqNode.inner = head
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
valueNode.parentFreqNode = head
|
|
|
|
|
node := head.Value.(*frequencyNode)
|
|
|
|
|
head = node.valuesList.PushBack(valueNode)
|
|
|
|
|
valueNode.inner = head
|
|
|
|
|
|
|
|
|
|
lfu.lookupTable[key] = valueNode
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Evict can be called to manually perform eviction
|
|
|
|
|
func (lfu *FrameCache) Evict() error {
|
|
|
|
|
lfu.rwLock.Lock()
|
|
|
|
|
defer lfu.rwLock.Unlock()
|
|
|
|
|
|
|
|
|
|
return lfu.unsafeEvict()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (lfu *FrameCache) unsafeUpdateFrequency(valueNode *keyRefNode) {
|
|
|
|
|
parentFreqNode := valueNode.parentFreqNode
|
|
|
|
|
currentNode := parentFreqNode.Value.(*frequencyNode)
|
|
|
|
|
nextParentFreqNode := parentFreqNode.Next()
|
|
|
|
|
|
|
|
|
|
var newParent *list.Element = nil
|
|
|
|
|
|
|
|
|
|
if nextParentFreqNode == nil {
|
|
|
|
|
// this is the last node
|
|
|
|
|
// create a new node with frequency + 1
|
|
|
|
|
newFreqNode := newFrequencyNode(currentNode.count + 1)
|
|
|
|
|
lfu.frequencies.PushBack(newFreqNode)
|
|
|
|
|
newParent = parentFreqNode.Next()
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
nextNode := nextParentFreqNode.Value.(*frequencyNode)
|
|
|
|
|
if nextNode.count == (currentNode.count + 1) {
|
|
|
|
|
newParent = nextParentFreqNode
|
|
|
|
|
} else {
|
|
|
|
|
// insert a node in between
|
|
|
|
|
newFreqNode := newFrequencyNode(currentNode.count + 1)
|
|
|
|
|
|
|
|
|
|
lfu.frequencies.InsertAfter(newFreqNode, parentFreqNode)
|
|
|
|
|
newParent = parentFreqNode.Next()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// remove from the existing list
|
|
|
|
|
currentNode.valuesList.Remove(valueNode.inner)
|
|
|
|
|
|
|
|
|
|
newParentNode := newParent.Value.(*frequencyNode)
|
|
|
|
|
valueNode.parentFreqNode = newParent
|
|
|
|
|
newValueNode := newParentNode.valuesList.PushBack(valueNode)
|
|
|
|
|
valueNode.inner = newValueNode
|
|
|
|
|
|
|
|
|
|
// check if the current node is empty
|
|
|
|
|
if currentNode.valuesList.Len() == 0 {
|
|
|
|
|
// remove the current node
|
|
|
|
|
lfu.frequencies.Remove(parentFreqNode)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get can be called to obtain the value for given key
|
|
|
|
|
func (lfu *FrameCache) Get(key uint) (*Frame, bool) {
|
|
|
|
|
lfu.rwLock.Lock()
|
|
|
|
|
defer lfu.rwLock.Unlock()
|
|
|
|
|
|
|
|
|
|
// check if data is in the map
|
|
|
|
|
valueNode, found := lfu.lookupTable[key]
|
|
|
|
|
if !found {
|
|
|
|
|
return nil, false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lfu.unsafeUpdateFrequency(valueNode)
|
|
|
|
|
|
|
|
|
|
return valueNode.valueRef, true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Delete removes the specified entry from LFU cache
|
|
|
|
|
func (lfu *FrameCache) Delete(key uint) error {
|
|
|
|
|
lfu.rwLock.Lock()
|
|
|
|
|
defer lfu.rwLock.Unlock()
|
|
|
|
|
|
|
|
|
|
// check if the key is in the map
|
|
|
|
|
valueNode, found := lfu.lookupTable[key]
|
|
|
|
|
if !found {
|
|
|
|
|
return fmt.Errorf("key %v not found", key)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
parentFreqNode := valueNode.parentFreqNode
|
|
|
|
|
|
|
|
|
|
currentNode := (parentFreqNode.Value).(*frequencyNode)
|
|
|
|
|
currentNode.valuesList.Remove(valueNode.inner)
|
|
|
|
|
|
|
|
|
|
if currentNode.valuesList.Len() == 0 {
|
|
|
|
|
lfu.frequencies.Remove(parentFreqNode)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
delete(lfu.lookupTable, key)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewFrameCache ...
|
|
|
|
|
func NewFrameCache(maxSize uint) *FrameCache {
|
|
|
|
|
return &FrameCache{
|
|
|
|
|
rwLock: sync.RWMutex{},
|
|
|
|
|
lookupTable: make(map[uint]*keyRefNode),
|
|
|
|
|
maxSize: maxSize,
|
|
|
|
|
frequencies: list.New(),
|
|
|
|
|
}
|
|
|
|
|
}
|