-
Notifications
You must be signed in to change notification settings - Fork 178
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
internal/lru: add a really simple LRU cache implementation
To replace github.com/hashicorp/golang-lru. The size used with the cache in fetchdatasource is 100, so the efficiency of the cache is not super important. For golang/go#61399 Change-Id: I48383a4d1c00c4d153c0bad7b0a1a44c026b2314 Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/524458 TryBot-Result: Gopher Robot <gobot@golang.org> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Run-TryBot: Michael Matloob <matloob@golang.org> Reviewed-by: Robert Findley <rfindley@google.com> kokoro-CI: kokoro <noreply+kokoro@google.com>
- Loading branch information
Showing
5 changed files
with
183 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
// Copyright 2023 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
// Package lru provides an LRU cache. | ||
package lru | ||
|
||
import ( | ||
"fmt" | ||
"math" | ||
"sync" | ||
) | ||
|
||
// Cache is an LRU cache. | ||
type Cache[K comparable, V any] struct { | ||
mu sync.Mutex | ||
size int | ||
entries map[K]*entry[V] | ||
tick uint // increases every time an entry is used | ||
} | ||
|
||
type entry[V any] struct { | ||
lastUsed uint // the tick of the last operation | ||
v V | ||
} | ||
|
||
// New returns a new Cache. Size must be positive or it will panic. | ||
func New[K comparable, V any](size int) *Cache[K, V] { | ||
if size < 1 { | ||
panic(fmt.Errorf("lru.New called with non-positive size %v", size)) | ||
} | ||
return &Cache[K, V]{ | ||
size: size, | ||
entries: map[K]*entry[V]{}, | ||
} | ||
} | ||
|
||
// Get gets the entry for k in the Cache. | ||
func (c *Cache[K, V]) Get(k K) (V, bool) { | ||
c.mu.Lock() | ||
defer c.mu.Unlock() | ||
entry, ok := c.entries[k] | ||
if !ok { | ||
var zero V | ||
return zero, false | ||
} | ||
c.tick++ | ||
entry.lastUsed = c.tick | ||
return entry.v, true | ||
} | ||
|
||
// Put puts in an entry for k, v in Cache, evicting | ||
// the least recently used entry if necessary. | ||
func (c *Cache[K, V]) Put(k K, v V) { | ||
c.mu.Lock() | ||
defer c.mu.Unlock() | ||
_, ok := c.entries[k] | ||
if !ok { | ||
// k not already in c.entries. We need to evict the least recently | ||
// used entry. | ||
if c.size < 1 { | ||
panic("attempting to insert into an uninitialized cache.") | ||
} | ||
if len(c.entries) > c.size { | ||
panic(fmt.Errorf("size of cache, %d, has grown beyond size limit %d", len(c.entries), c.size)) | ||
} | ||
if len(c.entries) == c.size { | ||
// evict least recently used element. | ||
var oldestTick uint = math.MaxUint | ||
var oldestKey K | ||
for k, e := range c.entries { | ||
if e.lastUsed <= oldestTick { | ||
oldestTick = e.lastUsed | ||
oldestKey = k | ||
} | ||
} | ||
delete(c.entries, oldestKey) | ||
} | ||
} | ||
c.tick++ | ||
c.entries[k] = &entry[V]{lastUsed: c.tick, v: v} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
// Copyright 2023 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package lru | ||
|
||
import "testing" | ||
|
||
func TestSizeOne(t *testing.T) { | ||
c := New[int, int](1) | ||
c.Put(1, 1) | ||
got, gotOK := c.Get(1) | ||
if got != 1 && gotOK != true { | ||
t.Errorf("c.Get(1): got %v, %v, want %v, %v", got, gotOK, 1, true) | ||
} | ||
c.Put(2, 2) | ||
got, gotOK = c.Get(1) | ||
if got != 0 || gotOK != false { | ||
t.Errorf("c.Get(1): got %v, %v, want %v, %v", got, gotOK, 0, false) | ||
} | ||
got, gotOK = c.Get(2) | ||
if got != 2 && gotOK != true { | ||
t.Errorf("c.Get(2): got %v, %v, want %v, %v", got, gotOK, 2, true) | ||
} | ||
} | ||
|
||
func TestSizeFive(t *testing.T) { | ||
c := New[int, int](5) | ||
c.Put(1, 1) | ||
c.Put(2, 2) | ||
c.Put(3, 3) | ||
c.Put(4, 4) | ||
c.Put(5, 5) | ||
|
||
getHasKey := func(k int, has bool) { | ||
t.Helper() | ||
|
||
got, ok := c.Get(k) | ||
if has == false { | ||
if ok == true || got != 0 { | ||
t.Errorf("c.Get(%v): got %v, %v, want %v, %v", k, got, ok, 0, false) | ||
} | ||
} else if got != k || ok != true { | ||
t.Errorf("c.Get(%v): got %v, %v, want %v, %v", k, got, ok, k, true) | ||
} | ||
} | ||
|
||
getHasKey(3, true) | ||
getHasKey(2, true) | ||
getHasKey(1, true) | ||
getHasKey(5, true) | ||
getHasKey(4, true) | ||
c.Put(6, 6) // 3 gets evicted | ||
|
||
getHasKey(3, false) | ||
getHasKey(1, true) | ||
getHasKey(2, true) | ||
getHasKey(4, true) | ||
getHasKey(5, true) | ||
getHasKey(6, true) | ||
c.Put(7, 7) | ||
c.Put(8, 8) // 1 and 2 get evicted | ||
|
||
getHasKey(1, false) | ||
getHasKey(2, false) | ||
getHasKey(8, true) | ||
getHasKey(7, true) | ||
getHasKey(4, true) | ||
getHasKey(5, true) | ||
getHasKey(6, true) | ||
c.Put(9, 9) // 8 gets evicted | ||
c.Put(10, 10) // 7 gets evicted | ||
c.Put(11, 11) // 4 gets evicted | ||
c.Put(12, 12) // 5 gets evicted | ||
c.Put(13, 13) // 6 gets evicted | ||
c.Put(14, 14) // 9 gets evicted | ||
|
||
getHasKey(4, false) | ||
getHasKey(5, false) | ||
getHasKey(6, false) | ||
getHasKey(7, false) | ||
getHasKey(8, false) | ||
getHasKey(9, false) | ||
getHasKey(10, true) | ||
getHasKey(11, true) | ||
getHasKey(12, true) | ||
getHasKey(13, true) | ||
getHasKey(14, true) | ||
c.Put(12, 12) | ||
|
||
getHasKey(10, true) | ||
getHasKey(11, true) | ||
getHasKey(12, true) | ||
getHasKey(13, true) | ||
getHasKey(14, true) | ||
} |