bunch of changes
Some checks failed
Build / build (push) Failing after 5m54s
Publish Image / publish (push) Failing after 35s
Test / build (push) Failing after 5m46s

This commit is contained in:
Chuck Smith
2024-03-28 17:19:23 -04:00
parent 244b71d245
commit fb0cebaf56
16 changed files with 174 additions and 320 deletions

View File

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

View File

@@ -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 ...

View File

@@ -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,