Skip to content

Commit

Permalink
Add support for generics (#111)
Browse files Browse the repository at this point in the history
  • Loading branch information
SoMuchForSubtlety committed Nov 14, 2022
1 parent bdf35e3 commit 9353717
Show file tree
Hide file tree
Showing 12 changed files with 346 additions and 191 deletions.
47 changes: 24 additions & 23 deletions 2q.go
Expand Up @@ -4,7 +4,7 @@ import (
"fmt"
"sync"

"github.com/hashicorp/golang-lru/simplelru"
"github.com/hashicorp/golang-lru/v2/simplelru"
)

const (
Expand All @@ -26,25 +26,25 @@ const (
// computationally about 2x the cost, and adds some metadata over
// head. The ARCCache is similar, but does not require setting any
// parameters.
type TwoQueueCache struct {
type TwoQueueCache[K comparable, V any] struct {
size int
recentSize int

recent simplelru.LRUCache
frequent simplelru.LRUCache
recentEvict simplelru.LRUCache
recent simplelru.LRUCache[K, V]
frequent simplelru.LRUCache[K, V]
recentEvict simplelru.LRUCache[K, V]
lock sync.RWMutex
}

// New2Q creates a new TwoQueueCache using the default
// values for the parameters.
func New2Q(size int) (*TwoQueueCache, error) {
return New2QParams(size, Default2QRecentRatio, Default2QGhostEntries)
func New2Q[K comparable, V any](size int) (*TwoQueueCache[K, V], error) {
return New2QParams[K, V](size, Default2QRecentRatio, Default2QGhostEntries)
}

// New2QParams creates a new TwoQueueCache using the provided
// parameter values.
func New2QParams(size int, recentRatio, ghostRatio float64) (*TwoQueueCache, error) {
func New2QParams[K comparable, V any](size int, recentRatio, ghostRatio float64) (*TwoQueueCache[K, V], error) {
if size <= 0 {
return nil, fmt.Errorf("invalid size")
}
Expand All @@ -60,21 +60,21 @@ func New2QParams(size int, recentRatio, ghostRatio float64) (*TwoQueueCache, err
evictSize := int(float64(size) * ghostRatio)

// Allocate the LRUs
recent, err := simplelru.NewLRU(size, nil)
recent, err := simplelru.NewLRU[K, V](size, nil)
if err != nil {
return nil, err
}
frequent, err := simplelru.NewLRU(size, nil)
frequent, err := simplelru.NewLRU[K, V](size, nil)
if err != nil {
return nil, err
}
recentEvict, err := simplelru.NewLRU(evictSize, nil)
recentEvict, err := simplelru.NewLRU[K, V](evictSize, nil)
if err != nil {
return nil, err
}

// Initialize the cache
c := &TwoQueueCache{
c := &TwoQueueCache[K, V]{
size: size,
recentSize: recentSize,
recent: recent,
Expand All @@ -85,7 +85,7 @@ func New2QParams(size int, recentRatio, ghostRatio float64) (*TwoQueueCache, err
}

// Get looks up a key's value from the cache.
func (c *TwoQueueCache) Get(key interface{}) (value interface{}, ok bool) {
func (c *TwoQueueCache[K, V]) Get(key K) (value V, ok bool) {
c.lock.Lock()
defer c.lock.Unlock()

Expand All @@ -103,11 +103,11 @@ func (c *TwoQueueCache) Get(key interface{}) (value interface{}, ok bool) {
}

// No hit
return nil, false
return
}

// Add adds a value to the cache.
func (c *TwoQueueCache) Add(key, value interface{}) {
func (c *TwoQueueCache[K, V]) Add(key K, value V) {
c.lock.Lock()
defer c.lock.Unlock()

Expand Down Expand Up @@ -141,7 +141,7 @@ func (c *TwoQueueCache) Add(key, value interface{}) {
}

// ensureSpace is used to ensure we have space in the cache
func (c *TwoQueueCache) ensureSpace(recentEvict bool) {
func (c *TwoQueueCache[K, V]) ensureSpace(recentEvict bool) {
// If we have space, nothing to do
recentLen := c.recent.Len()
freqLen := c.frequent.Len()
Expand All @@ -153,7 +153,8 @@ func (c *TwoQueueCache) ensureSpace(recentEvict bool) {
// the target, evict from there
if recentLen > 0 && (recentLen > c.recentSize || (recentLen == c.recentSize && !recentEvict)) {
k, _, _ := c.recent.RemoveOldest()
c.recentEvict.Add(k, nil)
var empty V
c.recentEvict.Add(k, empty)
return
}

Expand All @@ -162,15 +163,15 @@ func (c *TwoQueueCache) ensureSpace(recentEvict bool) {
}

// Len returns the number of items in the cache.
func (c *TwoQueueCache) Len() int {
func (c *TwoQueueCache[K, V]) Len() int {
c.lock.RLock()
defer c.lock.RUnlock()
return c.recent.Len() + c.frequent.Len()
}

// Keys returns a slice of the keys in the cache.
// The frequently used keys are first in the returned slice.
func (c *TwoQueueCache) Keys() []interface{} {
func (c *TwoQueueCache[K, V]) Keys() []K {
c.lock.RLock()
defer c.lock.RUnlock()
k1 := c.frequent.Keys()
Expand All @@ -179,7 +180,7 @@ func (c *TwoQueueCache) Keys() []interface{} {
}

// Remove removes the provided key from the cache.
func (c *TwoQueueCache) Remove(key interface{}) {
func (c *TwoQueueCache[K, V]) Remove(key K) {
c.lock.Lock()
defer c.lock.Unlock()
if c.frequent.Remove(key) {
Expand All @@ -194,7 +195,7 @@ func (c *TwoQueueCache) Remove(key interface{}) {
}

// Purge is used to completely clear the cache.
func (c *TwoQueueCache) Purge() {
func (c *TwoQueueCache[K, V]) Purge() {
c.lock.Lock()
defer c.lock.Unlock()
c.recent.Purge()
Expand All @@ -204,15 +205,15 @@ func (c *TwoQueueCache) Purge() {

// Contains is used to check if the cache contains a key
// without updating recency or frequency.
func (c *TwoQueueCache) Contains(key interface{}) bool {
func (c *TwoQueueCache[K, V]) Contains(key K) bool {
c.lock.RLock()
defer c.lock.RUnlock()
return c.frequent.Contains(key) || c.recent.Contains(key)
}

// Peek is used to inspect the cache value of a key
// without updating recency or frequency.
func (c *TwoQueueCache) Peek(key interface{}) (value interface{}, ok bool) {
func (c *TwoQueueCache[K, V]) Peek(key K) (value V, ok bool) {
c.lock.RLock()
defer c.lock.RUnlock()
if val, ok := c.frequent.Peek(key); ok {
Expand Down
18 changes: 9 additions & 9 deletions 2q_test.go
Expand Up @@ -5,7 +5,7 @@ import (
)

func Benchmark2Q_Rand(b *testing.B) {
l, err := New2Q(8192)
l, err := New2Q[int64, int64](8192)
if err != nil {
b.Fatalf("err: %v", err)
}
Expand Down Expand Up @@ -34,7 +34,7 @@ func Benchmark2Q_Rand(b *testing.B) {
}

func Benchmark2Q_Freq(b *testing.B) {
l, err := New2Q(8192)
l, err := New2Q[int64, int64](8192)
if err != nil {
b.Fatalf("err: %v", err)
}
Expand Down Expand Up @@ -67,7 +67,7 @@ func Benchmark2Q_Freq(b *testing.B) {

func Test2Q_RandomOps(t *testing.T) {
size := 128
l, err := New2Q(128)
l, err := New2Q[int64, int64](128)
if err != nil {
t.Fatalf("err: %v", err)
}
Expand All @@ -93,7 +93,7 @@ func Test2Q_RandomOps(t *testing.T) {
}

func Test2Q_Get_RecentToFrequent(t *testing.T) {
l, err := New2Q(128)
l, err := New2Q[int, int](128)
if err != nil {
t.Fatalf("err: %v", err)
}
Expand Down Expand Up @@ -139,7 +139,7 @@ func Test2Q_Get_RecentToFrequent(t *testing.T) {
}

func Test2Q_Add_RecentToFrequent(t *testing.T) {
l, err := New2Q(128)
l, err := New2Q[int, int](128)
if err != nil {
t.Fatalf("err: %v", err)
}
Expand Down Expand Up @@ -173,7 +173,7 @@ func Test2Q_Add_RecentToFrequent(t *testing.T) {
}

func Test2Q_Add_RecentEvict(t *testing.T) {
l, err := New2Q(4)
l, err := New2Q[int, int](4)
if err != nil {
t.Fatalf("err: %v", err)
}
Expand Down Expand Up @@ -220,7 +220,7 @@ func Test2Q_Add_RecentEvict(t *testing.T) {
}

func Test2Q(t *testing.T) {
l, err := New2Q(128)
l, err := New2Q[int, int](128)
if err != nil {
t.Fatalf("err: %v", err)
}
Expand Down Expand Up @@ -268,7 +268,7 @@ func Test2Q(t *testing.T) {

// Test that Contains doesn't update recent-ness
func Test2Q_Contains(t *testing.T) {
l, err := New2Q(2)
l, err := New2Q[int, int](2)
if err != nil {
t.Fatalf("err: %v", err)
}
Expand All @@ -287,7 +287,7 @@ func Test2Q_Contains(t *testing.T) {

// Test that Peek doesn't update recent-ness
func Test2Q_Peek(t *testing.T) {
l, err := New2Q(2)
l, err := New2Q[int, int](2)
if err != nil {
t.Fatalf("err: %v", err)
}
Expand Down

0 comments on commit 9353717

Please sign in to comment.