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 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 `` 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(), } }