Skip to content

Commit 5b89c9f

Browse files
committed
added clock package
1 parent 823ecf5 commit 5b89c9f

File tree

3 files changed

+321
-0
lines changed

3 files changed

+321
-0
lines changed

clock/clock.go

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
package clock
2+
3+
import (
4+
"container/ring"
5+
)
6+
7+
// Cache is used The clock cache replacement policy.
8+
//
9+
// The clock algorithm keeps a circular list of pages in memory, with
10+
// the "hand" (iterator) pointing to the last examined page frame in the list.
11+
// When a page fault occurs and no empty frames exist, then the R (referenced) bit
12+
// is inspected at the hand's location. If R is 0, the new page is put in place of
13+
// the page the "hand" points to, and the hand is advanced one position. Otherwise,
14+
// the R bit is cleared, then the clock hand is incremented and the process is
15+
// repeated until a page is replaced.
16+
type Cache[K comparable, V any] struct {
17+
items map[K]*ring.Ring
18+
hand *ring.Ring
19+
head *ring.Ring
20+
capacity int
21+
}
22+
23+
type entry[K comparable, V any] struct {
24+
key K
25+
val V
26+
referenceCount int
27+
}
28+
29+
// Option is an option for clock cache.
30+
type Option func(*options)
31+
32+
type options struct {
33+
capacity int
34+
}
35+
36+
func newOptions() *options {
37+
return &options{
38+
capacity: 128,
39+
}
40+
}
41+
42+
// WithCapacity is an option to set cache capacity.
43+
func WithCapacity(cap int) Option {
44+
return func(o *options) {
45+
o.capacity = cap
46+
}
47+
}
48+
49+
// NewCache creates a new non-thread safe clock cache whose capacity is the default size (128).
50+
func NewCache[K comparable, V any](opts ...Option) *Cache[K, V] {
51+
o := newOptions()
52+
for _, optFunc := range opts {
53+
optFunc(o)
54+
}
55+
r := ring.New(o.capacity)
56+
return &Cache[K, V]{
57+
items: make(map[K]*ring.Ring, o.capacity),
58+
hand: r,
59+
head: r,
60+
capacity: o.capacity,
61+
}
62+
}
63+
64+
// Set sets any item to the cache. replacing any existing item.
65+
func (c *Cache[K, V]) Set(key K, val V) {
66+
if e, ok := c.items[key]; ok {
67+
entry := e.Value.(*entry[K, V])
68+
entry.referenceCount++
69+
entry.val = val
70+
return
71+
}
72+
c.evict()
73+
c.hand.Value = &entry[K, V]{
74+
key: key,
75+
val: val,
76+
referenceCount: 1,
77+
}
78+
c.items[key] = c.hand
79+
c.hand = c.hand.Next()
80+
}
81+
82+
// Get looks up a key's value from the cache.
83+
func (c *Cache[K, V]) Get(key K) (zero V, _ bool) {
84+
e, ok := c.items[key]
85+
if !ok {
86+
return
87+
}
88+
entry := e.Value.(*entry[K, V])
89+
entry.referenceCount = 1
90+
return entry.val, true
91+
}
92+
93+
func (c *Cache[K, V]) evict() {
94+
if c.hand.Value == nil {
95+
return
96+
}
97+
for c.hand.Value.(*entry[K, V]).referenceCount > 0 {
98+
c.hand.Value.(*entry[K, V]).referenceCount--
99+
c.hand = c.hand.Next()
100+
}
101+
entry := c.hand.Value.(*entry[K, V])
102+
delete(c.items, entry.key)
103+
c.hand.Value = nil
104+
}
105+
106+
// Keys returns the keys of the cache. the order as same as current ring order.
107+
func (c *Cache[K, V]) Keys() []K {
108+
keys := make([]K, 0, len(c.items))
109+
r := c.head
110+
if r.Value == nil {
111+
return []K{}
112+
}
113+
// the first element
114+
keys = append(keys, r.Value.(*entry[K, V]).key)
115+
116+
// iterating
117+
for p := c.head.Next(); p != r; p = p.Next() {
118+
if p.Value == nil {
119+
break
120+
}
121+
e := p.Value.(*entry[K, V])
122+
if e.referenceCount > 0 {
123+
keys = append(keys, e.key)
124+
}
125+
}
126+
return keys
127+
}
128+
129+
// Delete deletes the item with provided key from the cache.
130+
func (c *Cache[K, V]) Delete(key K) {
131+
if e, ok := c.items[key]; ok {
132+
delete(c.items, key)
133+
e.Value.(*entry[K, V]).referenceCount = 0
134+
}
135+
}
136+
137+
// Len returns the number of items in the cache.
138+
func (c *Cache[K, V]) Len() int {
139+
return len(c.items)
140+
}

clock/clock_test.go

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package clock_test
2+
3+
import (
4+
"strings"
5+
"testing"
6+
7+
"github.com/Code-Hex/go-generics-cache/clock"
8+
)
9+
10+
func TestSet(t *testing.T) {
11+
// set capacity is 1
12+
cache := clock.NewCache[string, int](clock.WithCapacity(1))
13+
cache.Set("foo", 1)
14+
if got := cache.Len(); got != 1 {
15+
t.Fatalf("invalid length: %d", got)
16+
}
17+
if got, ok := cache.Get("foo"); got != 1 || !ok {
18+
t.Fatalf("invalid value got %d, cachehit %v", got, ok)
19+
}
20+
21+
// if over the cap
22+
cache.Set("bar", 2)
23+
if got := cache.Len(); got != 1 {
24+
t.Fatalf("invalid length: %d", got)
25+
}
26+
bar, ok := cache.Get("bar")
27+
if bar != 2 || !ok {
28+
t.Fatalf("invalid value bar %d, cachehit %v", bar, ok)
29+
}
30+
31+
// checks deleted oldest
32+
if _, ok := cache.Get("foo"); ok {
33+
t.Fatalf("invalid eviction the oldest value for foo %v", ok)
34+
}
35+
36+
// valid: if over the cap but same key
37+
cache.Set("bar", 100)
38+
if got := cache.Len(); got != 1 {
39+
t.Fatalf("invalid length: %d", got)
40+
}
41+
bar, ok = cache.Get("bar")
42+
if bar != 100 || !ok {
43+
t.Fatalf("invalid replacing value bar %d, cachehit %v", bar, ok)
44+
}
45+
}
46+
47+
func TestDelete(t *testing.T) {
48+
cache := clock.NewCache[string, int](clock.WithCapacity(1))
49+
cache.Set("foo", 1)
50+
if got := cache.Len(); got != 1 {
51+
t.Fatalf("invalid length: %d", got)
52+
}
53+
54+
cache.Delete("foo2")
55+
if got := cache.Len(); got != 1 {
56+
t.Fatalf("invalid length after deleted does not exist key: %d", got)
57+
}
58+
59+
cache.Delete("foo")
60+
if got := cache.Len(); got != 0 {
61+
t.Fatalf("invalid length after deleted: %d", got)
62+
}
63+
if _, ok := cache.Get("foo"); ok {
64+
t.Fatalf("invalid get after deleted %v", ok)
65+
}
66+
}
67+
68+
func TestKeys(t *testing.T) {
69+
t.Run("normal", func(t *testing.T) {
70+
cache := clock.NewCache[string, int]()
71+
if len(cache.Keys()) != 0 {
72+
t.Errorf("want number of keys 0, but got %d", cache.Len())
73+
}
74+
75+
cache.Set("foo", 1)
76+
cache.Set("bar", 2)
77+
cache.Set("baz", 3)
78+
cache.Set("bar", 4) // again
79+
cache.Set("foo", 5) // again
80+
81+
got := strings.Join(cache.Keys(), ",")
82+
want := strings.Join([]string{
83+
"foo",
84+
"bar",
85+
"baz",
86+
}, ",")
87+
if got != want {
88+
t.Errorf("want %q, but got %q", want, got)
89+
}
90+
if len(cache.Keys()) != cache.Len() {
91+
t.Errorf("want number of keys %d, but got %d", len(cache.Keys()), cache.Len())
92+
}
93+
})
94+
95+
t.Run("with deletion", func(t *testing.T) {
96+
cache := clock.NewCache[string, int](clock.WithCapacity(4))
97+
cache.Set("foo", 1)
98+
cache.Set("bar", 2)
99+
cache.Set("baz", 3)
100+
101+
cache.Delete("bar") // delete in middle
102+
103+
got := strings.Join(cache.Keys(), ",")
104+
want := strings.Join([]string{
105+
"foo",
106+
"baz",
107+
}, ",")
108+
if got != want {
109+
t.Errorf("want %q, but got %q", want, got)
110+
}
111+
if len(cache.Keys()) != cache.Len() {
112+
t.Errorf("want number of keys %d, but got %d", len(cache.Keys()), cache.Len())
113+
}
114+
115+
cache.Set("hoge", 4)
116+
cache.Set("fuga", 5) // over the cap. so expected to set "bar" position.
117+
118+
got2 := strings.Join(cache.Keys(), ",")
119+
want2 := strings.Join([]string{
120+
"foo",
121+
"fuga",
122+
"baz",
123+
"hoge",
124+
}, ",")
125+
if got2 != want2 {
126+
t.Errorf("want2 %q, but got2 %q", want, got)
127+
}
128+
if len(cache.Keys()) != cache.Len() {
129+
t.Errorf("want2 number of keys %d, but got2 %d", len(cache.Keys()), cache.Len())
130+
}
131+
})
132+
}

clock/example_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package clock_test
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/Code-Hex/go-generics-cache/clock"
7+
)
8+
9+
func ExampleCache() {
10+
c := clock.NewCache[string, int]()
11+
c.Set("a", 1)
12+
c.Set("b", 2)
13+
av, aok := c.Get("a")
14+
bv, bok := c.Get("b")
15+
cv, cok := c.Get("c")
16+
fmt.Println(av, aok)
17+
fmt.Println(bv, bok)
18+
fmt.Println(cv, cok)
19+
c.Delete("a")
20+
_, aok2 := c.Get("a")
21+
if !aok2 {
22+
fmt.Println("key 'a' has been deleted")
23+
}
24+
// update
25+
c.Set("b", 3)
26+
newbv, _ := c.Get("b")
27+
fmt.Println(newbv)
28+
// Output:
29+
// 1 true
30+
// 2 true
31+
// 0 false
32+
// key 'a' has been deleted
33+
// 3
34+
}
35+
36+
func ExampleCacheKeys() {
37+
c := clock.NewCache[string, int]()
38+
c.Set("foo", 1)
39+
c.Set("bar", 2)
40+
c.Set("baz", 3)
41+
keys := c.Keys()
42+
for _, key := range keys {
43+
fmt.Println(key)
44+
}
45+
// Output:
46+
// foo
47+
// bar
48+
// baz
49+
}

0 commit comments

Comments
 (0)