Golang implementation of the [Sieve cache algorithm][1]. [1]: https://cachemon.github.io/SIEVE-website/blog/2023/12/17/sieve-is-simpler-than-lru/main
commit
511bdc61c5
@ -0,0 +1,98 @@
|
||||
package sieve
|
||||
|
||||
type node struct {
|
||||
value string
|
||||
visited bool
|
||||
prev, next *node
|
||||
}
|
||||
|
||||
type SieveCache struct {
|
||||
cap, size int
|
||||
cache map[string]*node
|
||||
head, tail, hand *node
|
||||
}
|
||||
|
||||
func NewCache(cap int) *SieveCache {
|
||||
return &SieveCache{
|
||||
cap: cap,
|
||||
size: 0,
|
||||
cache: make(map[string]*node, cap),
|
||||
}
|
||||
}
|
||||
|
||||
// Access touches the item `v` in the cache
|
||||
func (sc *SieveCache) Access(v string) {
|
||||
if n, ok := sc.cache[v]; ok {
|
||||
n.visited = true
|
||||
} else {
|
||||
if sc.size == sc.cap {
|
||||
sc.evict()
|
||||
}
|
||||
|
||||
newNode := &node{value: v}
|
||||
sc.addToHead(newNode)
|
||||
sc.cache[v] = newNode
|
||||
sc.size += 1
|
||||
newNode.visited = false
|
||||
}
|
||||
}
|
||||
|
||||
// Range iterates over the values in the cache, stops the iteration if the
|
||||
// closure returns false.
|
||||
func (sc *SieveCache) Range(f func(v string) bool) {
|
||||
current := sc.head
|
||||
keepGoing := true
|
||||
for current != nil && keepGoing {
|
||||
keepGoing = f(current.value)
|
||||
current = current.next
|
||||
}
|
||||
}
|
||||
|
||||
func (sc *SieveCache) evict() {
|
||||
var obj *node
|
||||
if sc.hand != nil {
|
||||
obj = sc.hand
|
||||
} else {
|
||||
obj = sc.tail
|
||||
}
|
||||
|
||||
for obj != nil && obj.visited {
|
||||
obj.visited = false
|
||||
if obj.prev != nil {
|
||||
obj = obj.prev
|
||||
} else {
|
||||
obj = sc.tail
|
||||
}
|
||||
}
|
||||
|
||||
sc.hand = obj.prev
|
||||
delete(sc.cache, obj.value)
|
||||
sc.removeNode(obj)
|
||||
sc.size -= 1
|
||||
}
|
||||
|
||||
func (sc *SieveCache) removeNode(n *node) {
|
||||
if n.prev != nil {
|
||||
n.prev.next = n.next
|
||||
} else {
|
||||
sc.head = n.next
|
||||
}
|
||||
|
||||
if n.next != nil {
|
||||
n.next.prev = n.prev
|
||||
} else {
|
||||
sc.tail = n.prev
|
||||
}
|
||||
}
|
||||
|
||||
func (sc *SieveCache) addToHead(n *node) {
|
||||
n.next = sc.head
|
||||
n.prev = nil
|
||||
if sc.head != nil {
|
||||
sc.head.prev = n
|
||||
}
|
||||
sc.head = n
|
||||
if sc.tail == nil {
|
||||
sc.tail = n
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package sieve
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestSieveCache(t *testing.T) {
|
||||
cache := NewCache(3)
|
||||
cache.Access("A")
|
||||
cache.Access("B")
|
||||
cache.Access("C")
|
||||
cache.Access("D")
|
||||
cache.Access("B")
|
||||
cache.Access("E")
|
||||
cache.Access("A")
|
||||
cache.Range(func(v string) bool {
|
||||
t.Log(v)
|
||||
return true
|
||||
})
|
||||
}
|
Loading…
Reference in new issue