Skip to content

Commit

Permalink
Add Random eviction policy (#55)
Browse files Browse the repository at this point in the history
  • Loading branch information
erni27 authored Jan 14, 2024
1 parent 50ebc73 commit 62fa76e
Show file tree
Hide file tree
Showing 3 changed files with 347 additions and 53 deletions.
52 changes: 52 additions & 0 deletions eviction.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package imcache

import "math/rand"

// EvictionPolicy represents the eviction policy.
type EvictionPolicy int32

Expand All @@ -8,6 +10,9 @@ const (
EvictionPolicyLRU EvictionPolicy = iota + 1
// EvictionPolicyLFU is the least frequently used eviction policy.
EvictionPolicyLFU
// EvictionPolicyRandom is the random eviction policy.
// It evicts the entry randomly when the max entries limit exceeded.
EvictionPolicyRandom
)

// EvictionReason is the reason why an entry was evicted.
Expand Down Expand Up @@ -73,6 +78,9 @@ func newEvictionQueue[K comparable, V any](limit int, policy EvictionPolicy) evi
return &lruEvictionQueue[K, V]{}
case EvictionPolicyLFU:
return &lfuEvictionQueue[K, V]{freqs: make(map[int]*lfuLruEvictionQueue[K, V])}
case EvictionPolicyRandom:
var q randomEvictionQueue[K, V] = make([]*randomNode[K, V], 0, limit)
return &q
}
return nopEvictionQueue[K, V]{}
}
Expand Down Expand Up @@ -318,6 +326,50 @@ func (q *lfuEvictionQueue[K, V]) touchall() {
}
}

//lint:ignore U1000 false positive
type randomNode[K comparable, V any] struct {
entr entry[K, V]
idx int
}

func (n *randomNode[K, V]) entry() entry[K, V] {
return n.entr
}

func (n *randomNode[K, V]) setEntry(e entry[K, V]) {
n.entr = e
}

//lint:ignore U1000 false positive
type randomEvictionQueue[K comparable, V any] []*randomNode[K, V]

func (q *randomEvictionQueue[K, V]) add(e entry[K, V]) node[K, V] {
n := &randomNode[K, V]{entr: e}
*q = append(*q, n)
n.idx = len(*q) - 1
return n
}

func (q *randomEvictionQueue[K, V]) remove(n node[K, V]) {
randn := n.(*randomNode[K, V])
updated := *q
updated[randn.idx], updated[len(updated)-1] = updated[len(updated)-1], updated[randn.idx]
updated[randn.idx].idx = randn.idx
updated = updated[:len(updated)-1]
*q = updated
}

func (q *randomEvictionQueue[K, V]) pop() node[K, V] {
idx := rand.Intn(len(*q))
n := (*q)[idx]
q.remove(n)
return n
}

func (randomEvictionQueue[K, V]) touch(node[K, V]) {}

func (randomEvictionQueue[K, V]) touchall() {}

//lint:ignore U1000 false positive
type nopNode[K comparable, V any] entry[K, V]

Expand Down
126 changes: 126 additions & 0 deletions imcache_benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,40 @@ func BenchmarkSharded_Get_MaxEntriesLimit_EvictionPolicyLFU(b *testing.B) {
}
}

func BenchmarkCache_Get_MaxEntriesLimit_EvictionPolicyRandom(b *testing.B) {
c := New[string, token](WithMaxEntriesLimitOption[string, token](b.N, EvictionPolicyRandom))
for i := 0; i < b.N; i++ {
c.Set(fmt.Sprintf("key-%d", i), token{ID: i}, WithNoExpiration())
}

b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
if v, ok := c.Get(fmt.Sprintf("key-%d", random.Intn(b.N))); ok {
_ = v
}
}
}

func BenchmarkSharded_Get_MaxEntriesLimit_EvictionPolicyRandom(b *testing.B) {
for _, n := range []int{2, 4, 8, 16, 32, 64, 128} {
b.Run(fmt.Sprintf("%d_Shards", n), func(b *testing.B) {
c := NewSharded[string, token](n, DefaultStringHasher64{}, WithMaxEntriesLimitOption[string, token](b.N/n, EvictionPolicyRandom))
for i := 0; i < b.N; i++ {
c.Set(fmt.Sprintf("key-%d", i), token{ID: i}, WithNoExpiration())
}

b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
if v, ok := c.Get(fmt.Sprintf("key-%d", random.Intn(b.N))); ok {
_ = v
}
}
})
}
}

func BenchmarkMap_Get(b *testing.B) {
m := make(map[string]token)
var mu sync.Mutex
Expand Down Expand Up @@ -245,6 +279,44 @@ func BenchmarkSharded_Get_MaxEntriesLimit_EvictionPolicyLFU_Parallel(b *testing.
}
}

func BenchmarkCache_Get_MaxEntriesLimit_EvictionPolicyRandom_Parallel(b *testing.B) {
c := New[string, token](WithMaxEntriesLimitOption[string, token](b.N, EvictionPolicyRandom))
for i := 0; i < b.N; i++ {
c.Set(fmt.Sprintf("key-%d", i), token{ID: i}, WithNoExpiration())
}

b.ReportAllocs()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
if v, ok := c.Get(fmt.Sprintf("key-%d", random.Intn(b.N))); ok {
_ = (token)(v)
}
}
})
}

func BenchmarkSharded_Get_MaxEntriesLimit_EvictionPolicyRandom_Parallel(b *testing.B) {
for _, n := range []int{2, 4, 8, 16, 32, 64, 128} {
b.Run(fmt.Sprintf("%d_Shards", n), func(b *testing.B) {
c := NewSharded[string, token](n, DefaultStringHasher64{}, WithMaxEntriesLimitOption[string, token](b.N/n, EvictionPolicyRandom))
for i := 0; i < b.N; i++ {
c.Set(fmt.Sprintf("key-%d", i), token{ID: i}, WithNoExpiration())
}

b.ReportAllocs()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
if v, ok := c.Get(fmt.Sprintf("key-%d", random.Intn(b.N))); ok {
_ = (token)(v)
}
}
})
})
}
}

func BenchmarkMap_Get_Parallel(b *testing.B) {
m := make(map[string]token)
var mu sync.Mutex
Expand Down Expand Up @@ -338,6 +410,30 @@ func BenchmarkSharded_Set_MaxEntriesLimit_EvictionPolicyLFU(b *testing.B) {
}
}

func BenchmarkCache_Set_MaxEntriesLimit_EvictionPolicyRandom(b *testing.B) {
c := New[string, token](WithMaxEntriesLimitOption[string, token](b.N/2, EvictionPolicyRandom))

b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
c.Set(fmt.Sprintf("key-%d", random.Intn(b.N)), token{ID: i}, WithNoExpiration())
}
}

func BenchmarkSharded_Set_MaxEntriesLimit_EvictionPolicyRandom(b *testing.B) {
for _, n := range []int{2, 4, 8, 16, 32, 64, 128} {
b.Run(fmt.Sprintf("%d_Shards", n), func(b *testing.B) {
c := NewSharded[string, token](n, DefaultStringHasher64{}, WithMaxEntriesLimitOption[string, token](b.N/n/2, EvictionPolicyRandom))

b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
c.Set(fmt.Sprintf("key-%d", random.Intn(b.N)), token{ID: i}, WithNoExpiration())
}
})
}
}

func BenchmarkMap_Set(b *testing.B) {
m := make(map[string]token)
var mu sync.Mutex
Expand Down Expand Up @@ -441,6 +537,36 @@ func BenchmarkSharded_Set_MaxEntriesLimit_EvictionPolicyLFU_Parallel(b *testing.
}
}

func BenchmarkCache_Set_MaxEntriesLimit_EvictionPolicyRandom_Parallel(b *testing.B) {
c := New[string, token](WithMaxEntriesLimitOption[string, token](b.N/2, EvictionPolicyRandom))

b.ReportAllocs()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
i := random.Intn(b.N)
c.Set(fmt.Sprintf("key-%d", i), token{ID: i}, WithNoExpiration())
}
})
}

func BenchmarkSharded_Set_MaxEntriesLimit_EvictionPolicyRandom_Parallel(b *testing.B) {
for _, n := range []int{2, 4, 8, 16, 32, 64, 128} {
b.Run(fmt.Sprintf("%d_Shards", n), func(b *testing.B) {
c := NewSharded[string, token](n, DefaultStringHasher64{}, WithMaxEntriesLimitOption[string, token](b.N/n/2, EvictionPolicyRandom))

b.ReportAllocs()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
i := random.Intn(b.N)
c.Set(fmt.Sprintf("key-%d", i), token{ID: i}, WithNoExpiration())
}
})
})
}
}

func BenchmarkMap_Set_Parallel(b *testing.B) {
m := make(map[string]token)
var mu sync.Mutex
Expand Down
Loading

0 comments on commit 62fa76e

Please sign in to comment.