Skip to content

Commit

Permalink
have separate generic and non-generic implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelMure committed Apr 13, 2023
1 parent f5c321b commit d4f2953
Show file tree
Hide file tree
Showing 6 changed files with 511 additions and 56 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/go.yml
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Set up Go 1.x
uses: actions/setup-go@v2
with:
go-version: ^1.13
go-version: ^1.18

- name: Check out code into the Go module directory
uses: actions/checkout@v2
Expand Down
4 changes: 3 additions & 1 deletion README.md
Expand Up @@ -18,9 +18,11 @@ This will retrieve the library.

### Usage

Note: this package also has a non-generic `tinylru.LRU` implementation.

```go
// Create an LRU cache
var cache tinylru.LRU[string, string]
var cache tinylru.LRUG[string, string]

// Set the cache size. This is the maximum number of items that the cache can
// hold before evicting old items. The default size is 256.
Expand Down
70 changes: 35 additions & 35 deletions lru.go
Expand Up @@ -10,47 +10,47 @@ import "sync"
// get automatically evicted.
const DefaultSize = 256

type lruItem[Key comparable, Value any] struct {
key Key // user-defined key
value Value // user-defined value
prev *lruItem[Key, Value] // prev item in list. More recently used
next *lruItem[Key, Value] // next item in list. Less recently used
type lruItem struct {
key interface{} // user-defined key
value interface{} // user-defined value
prev *lruItem // prev item in list. More recently used
next *lruItem // next item in list. Less recently used
}

// LRU implements an LRU cache
type LRU[Key comparable, Value any] struct {
mu sync.RWMutex // protect all things
size int // max number of items.
items map[Key]*lruItem[Key, Value] // active items
head *lruItem[Key, Value] // head of list
tail *lruItem[Key, Value] // tail of list
type LRU struct {
mu sync.RWMutex // protect all things
size int // max number of items.
items map[interface{}]*lruItem // active items
head *lruItem // head of list
tail *lruItem // tail of list
}

//go:noinline
func (lru *LRU[Key, Value]) init() {
lru.items = make(map[Key]*lruItem[Key, Value])
lru.head = new(lruItem[Key, Value])
lru.tail = new(lruItem[Key, Value])
func (lru *LRU) init() {
lru.items = make(map[interface{}]*lruItem)
lru.head = new(lruItem)
lru.tail = new(lruItem)
lru.head.next = lru.tail
lru.tail.prev = lru.head
if lru.size == 0 {
lru.size = DefaultSize
}
}

func (lru *LRU[Key, Value]) evict() *lruItem[Key, Value] {
func (lru *LRU) evict() *lruItem {
item := lru.tail.prev
lru.pop(item)
delete(lru.items, item.key)
return item
}

func (lru *LRU[Key, Value]) pop(item *lruItem[Key, Value]) {
func (lru *LRU) pop(item *lruItem) {
item.prev.next = item.next
item.next.prev = item.prev
}

func (lru *LRU[Key, Value]) push(item *lruItem[Key, Value]) {
func (lru *LRU) push(item *lruItem) {
lru.head.next.prev = item
item.next = lru.head.next
item.prev = lru.head
Expand All @@ -61,8 +61,8 @@ func (lru *LRU[Key, Value]) push(item *lruItem[Key, Value]) {
// the number of items currently in the cache, then items will be evicted.
// Returns evicted items.
// This operation will panic if the size is less than one.
func (lru *LRU[Key, Value]) Resize(size int) (evictedKeys []Key,
evictedValues []Value) {
func (lru *LRU) Resize(size int) (evictedKeys []interface{},
evictedValues []interface{}) {
if size <= 0 {
panic("invalid size")
}
Expand All @@ -79,17 +79,17 @@ func (lru *LRU[Key, Value]) Resize(size int) (evictedKeys []Key,
}

// Len returns the length of the lru cache
func (lru *LRU[Key, Value]) Len() int {
func (lru *LRU) Len() int {
lru.mu.RLock()
defer lru.mu.RUnlock()
return len(lru.items)
}

// SetEvicted sets or replaces a value for a key. If this operation causes an
// eviction then the evicted item is returned.
func (lru *LRU[Key, Value]) SetEvicted(key Key, value Value) (
prev Value, replaced bool, evictedKey Key,
evictedValue Value, evicted bool) {
func (lru *LRU) SetEvicted(key interface{}, value interface{}) (
prev interface{}, replaced bool, evictedKey interface{},
evictedValue interface{}, evicted bool) {
lru.mu.Lock()
defer lru.mu.Unlock()
if lru.items == nil {
Expand All @@ -101,7 +101,7 @@ func (lru *LRU[Key, Value]) SetEvicted(key Key, value Value) (
item = lru.evict()
evictedKey, evictedValue, evicted = item.key, item.value, true
} else {
item = new(lruItem[Key, Value])
item = new(lruItem)
}
item.key = key
item.value = value
Expand All @@ -119,19 +119,19 @@ func (lru *LRU[Key, Value]) SetEvicted(key Key, value Value) (
}

// Set or replace a value for a key.
func (lru *LRU[Key, Value]) Set(key Key, value Value) (prev Value,
func (lru *LRU) Set(key interface{}, value interface{}) (prev interface{},
replaced bool) {
prev, replaced, _, _, _ = lru.SetEvicted(key, value)
return prev, replaced
}

// Get a value for key
func (lru *LRU[Key, Value]) Get(key Key) (value Value, ok bool) {
func (lru *LRU) Get(key interface{}) (value interface{}, ok bool) {
lru.mu.Lock()
defer lru.mu.Unlock()
item := lru.items[key]
if item == nil {
return
return nil, false
}
if lru.head.next != item {
lru.pop(item)
Expand All @@ -141,7 +141,7 @@ func (lru *LRU[Key, Value]) Get(key Key) (value Value, ok bool) {
}

// Contains returns true if the key exists.
func (lru *LRU[Key, Value]) Contains(key Key) bool {
func (lru *LRU) Contains(key interface{}) bool {
lru.mu.RLock()
defer lru.mu.RUnlock()
_, ok := lru.items[key]
Expand All @@ -150,23 +150,23 @@ func (lru *LRU[Key, Value]) Contains(key Key) bool {

// Peek returns the value for key value without updating
// the recently used status.
func (lru *LRU[Key, Value]) Peek(key Key) (value Value, ok bool) {
func (lru *LRU) Peek(key interface{}) (value interface{}, ok bool) {
lru.mu.RLock()
defer lru.mu.RUnlock()

if item := lru.items[key]; item != nil {
return item.value, true
}
return
return nil, false
}

// Delete a value for a key
func (lru *LRU[Key, Value]) Delete(key Key) (prev Value, deleted bool) {
func (lru *LRU) Delete(key interface{}) (prev interface{}, deleted bool) {
lru.mu.Lock()
defer lru.mu.Unlock()
item := lru.items[key]
if item == nil {
return
return nil, false
}
delete(lru.items, key)
lru.pop(item)
Expand All @@ -176,7 +176,7 @@ func (lru *LRU[Key, Value]) Delete(key Key) (prev Value, deleted bool) {
// Range iterates over all key/values in the order of most recently to
// least recently used items.
// It's not safe to call other LRU operations while ranging.
func (lru *LRU[Key, Value]) Range(iter func(key Key, value Value) bool) {
func (lru *LRU) Range(iter func(key interface{}, value interface{}) bool) {
lru.mu.RLock()
defer lru.mu.RUnlock()
if head := lru.head; head != nil {
Expand All @@ -193,7 +193,7 @@ func (lru *LRU[Key, Value]) Range(iter func(key Key, value Value) bool) {
// Reverse iterates over all key/values in the order of least recently to
// most recently used items.
// It's not safe to call other LRU operations while ranging.
func (lru *LRU[Key, Value]) Reverse(iter func(key Key, value Value) bool) {
func (lru *LRU) Reverse(iter func(key interface{}, value interface{}) bool) {
lru.mu.RLock()
defer lru.mu.RUnlock()
if tail := lru.tail; tail != nil {
Expand Down
38 changes: 19 additions & 19 deletions lru_test.go
Expand Up @@ -14,7 +14,7 @@ func init() {
}

type tItem struct {
key string
key interface{}
val int
}

Expand All @@ -29,14 +29,14 @@ func TestLRU(t *testing.T) {
size := DefaultSize

// Set items
var cache LRU[string, int] = LRU[string, int]{}
var cache LRU
for i := 0; i < len(items); i++ {
prev, replaced, evictedKey, evictedValue, evicted :=
cache.SetEvicted(items[i].key, items[i].val)
if replaced {
t.Fatal("expected false")
}
if prev != 0 {
if prev != nil {
t.Fatal("expected nil")
}
if evicted {
Expand Down Expand Up @@ -78,8 +78,8 @@ func TestLRU(t *testing.T) {

idx := size - 1
res := make([]tItem, size)
cache.Range(func(key string, value int) bool {
res[idx] = tItem{key: key, val: value}
cache.Range(func(key, value interface{}) bool {
res[idx] = tItem{key: key, val: value.(int)}
idx--
return true
})
Expand All @@ -89,8 +89,8 @@ func TestLRU(t *testing.T) {
}
}
var recent tItem
cache.Range(func(key string, value int) bool {
recent = tItem{key: key, val: value}
cache.Range(func(key, value interface{}) bool {
recent = tItem{key: key, val: value.(int)}
return false
})
if items[len(items)-1] != recent {
Expand All @@ -99,8 +99,8 @@ func TestLRU(t *testing.T) {

idx = size - 1
res = make([]tItem, size)
cache.Reverse(func(key string, value int) bool {
res[idx] = tItem{key: key, val: value}
cache.Reverse(func(key, value interface{}) bool {
res[idx] = tItem{key: key, val: value.(int)}
idx--
return true
})
Expand All @@ -110,8 +110,8 @@ func TestLRU(t *testing.T) {
}
}
var least tItem
cache.Reverse(func(key string, value int) bool {
least = tItem{key: key, val: value}
cache.Reverse(func(key, value interface{}) bool {
least = tItem{key: key, val: value.(int)}
return false
})
if items[len(items)-size] != least {
Expand Down Expand Up @@ -139,7 +139,7 @@ func TestLRU(t *testing.T) {
if ok {
t.Fatal("expected false")
}
if value != 0 {
if value != nil {
t.Fatal("expected nil")
}
} else {
Expand All @@ -160,7 +160,7 @@ func TestLRU(t *testing.T) {
if ok {
t.Fatal("expected false")
}
if value != 0 {
if value != nil {
t.Fatal("expected nil")
}
} else {
Expand Down Expand Up @@ -219,15 +219,15 @@ func TestLRU(t *testing.T) {
if deleted {
t.Fatal("expected false")
}
if prev != 0 {
if prev != nil {
t.Fatal("expected nil")
}

prev, replaced := cache.Set("hello", 1)
prev, replaced := cache.Set("hello", "world")
if replaced {
t.Fatal("expected false")
}
if prev != 0 {
if prev != nil {
t.Fatal("expected nil")
}
}
Expand All @@ -239,18 +239,18 @@ func BenchmarkSet(b *testing.B) {
}
b.ResetTimer()
b.ReportAllocs()
var cache LRU[string, int]
var cache LRU
for i := 0; i < b.N; i++ {
cache.Set(items[i].key, items[i].val)
}
}

func TestLRUInt(t *testing.T) {
var cache LRU[int, int]
var cache LRU
cache.Set(123, 123)
cache.Set(123, 456)
v, _ := cache.Get(123)
if v != 456 {
if v.(int) != 456 {
t.Fatalf("expected %v, got %v", 456, v)
}
}

0 comments on commit d4f2953

Please sign in to comment.