bunch of changes
This commit is contained in:
@@ -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(),
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,8 @@ package vm
|
||||
import (
|
||||
"monkey/internal/code"
|
||||
"monkey/internal/object"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var cache = NewFrameCache(32)
|
||||
|
||||
type Frame struct {
|
||||
cl *object.Closure
|
||||
ip int
|
||||
@@ -15,21 +12,11 @@ type Frame struct {
|
||||
}
|
||||
|
||||
func NewFrame(cl *object.Closure, basePointer int) *Frame {
|
||||
key := uint(uintptr(unsafe.Pointer(cl))) + uint(basePointer)
|
||||
if frame, ok := cache.Get(key); ok {
|
||||
frame.Reset()
|
||||
return frame
|
||||
}
|
||||
|
||||
frame := &Frame{
|
||||
return &Frame{
|
||||
cl: cl,
|
||||
ip: -1,
|
||||
basePointer: basePointer,
|
||||
}
|
||||
|
||||
cache.Put(key, frame)
|
||||
|
||||
return frame
|
||||
}
|
||||
|
||||
// NextOp ...
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
@@ -201,22 +202,16 @@ func (vm *VM) LastPoppedStackElem() object.Object {
|
||||
}
|
||||
|
||||
func (vm *VM) Run() error {
|
||||
var n int
|
||||
var ip int
|
||||
var ins code.Instructions
|
||||
var op code.Opcode
|
||||
|
||||
if vm.Debug {
|
||||
log.Printf(
|
||||
"%-25s %-20s\n",
|
||||
fmt.Sprintf(
|
||||
"%04d %s", ip,
|
||||
strings.Split(ins[ip:].String(), "\n")[0][4:],
|
||||
),
|
||||
fmt.Sprintf(
|
||||
"[ip=%02d fp=%02d, sp=%02d]",
|
||||
ip, vm.fp-1, vm.sp,
|
||||
),
|
||||
)
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
log.Printf("%d instructions executed in %s", n, time.Now().Sub(start))
|
||||
}()
|
||||
}
|
||||
|
||||
for vm.frame.ip < len(vm.frame.Instructions())-1 {
|
||||
@@ -487,6 +482,7 @@ func (vm *VM) Run() error {
|
||||
}
|
||||
|
||||
if vm.Debug {
|
||||
n++
|
||||
log.Printf(
|
||||
"%-25s [ip=%02d fp=%02d, sp=%02d]",
|
||||
"", ip, vm.fp-1, vm.sp,
|
||||
|
||||
Reference in New Issue
Block a user