bloblru: Upgrade to hashicorp/golang-lru/v2

The new genericized LRU cache no longer needs to have the IDs separately
allocated:

name   old time/op    new time/op    delta
Add-8     494ns ± 2%     388ns ± 2%  -21.46%  (p=0.000 n=10+9)

name   old alloc/op   new alloc/op   delta
Add-8      176B ± 0%      152B ± 0%  -13.64%  (p=0.000 n=10+10)

name   old allocs/op  new allocs/op  delta
Add-8      5.00 ± 0%      3.00 ± 0%  -40.00%  (p=0.000 n=10+10)
This commit is contained in:
greatroar 2022-11-27 09:58:19 +01:00
parent 05cebc1c4b
commit e5d597fd22
4 changed files with 38 additions and 16 deletions

2
go.mod
View File

@ -9,7 +9,7 @@ require (
github.com/elithrar/simple-scrypt v1.3.0
github.com/go-ole/go-ole v1.2.6
github.com/google/go-cmp v0.5.9
github.com/hashicorp/golang-lru v0.5.4
github.com/hashicorp/golang-lru/v2 v2.0.1
github.com/juju/ratelimit v1.0.2
github.com/klauspost/compress v1.15.9
github.com/kurin/blazer v0.5.4-0.20211030221322-ba894c124ac6

4
go.sum
View File

@ -109,8 +109,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.2.0 h1:y8Yozv7SZtlU//QXbez
github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg=
github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ=
github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/golang-lru/v2 v2.0.1 h1:5pv5N1lT1fjLg2VQ5KWc7kmucp2x/kvFOnxuVTqZ6x4=
github.com/hashicorp/golang-lru/v2 v2.0.1/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=

View File

@ -6,7 +6,7 @@ import (
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/restic"
"github.com/hashicorp/golang-lru/simplelru"
"github.com/hashicorp/golang-lru/v2/simplelru"
)
// Crude estimate of the overhead per blob: a SHA-256, a linked list node
@ -17,7 +17,7 @@ const overhead = len(restic.ID{}) + 64
// It is safe for concurrent access.
type Cache struct {
mu sync.Mutex
c *simplelru.LRU
c *simplelru.LRU[restic.ID, []byte]
free, size int // Current and max capacity, in bytes.
}
@ -33,7 +33,7 @@ func New(size int) *Cache {
// The actual maximum will be smaller than size/overhead, because we
// evict entries (RemoveOldest in add) to maintain our size bound.
maxEntries := size / overhead
lru, err := simplelru.NewLRU(maxEntries, c.evict)
lru, err := simplelru.NewLRU[restic.ID, []byte](maxEntries, c.evict)
if err != nil {
panic(err) // Can only be maxEntries <= 0.
}
@ -55,24 +55,21 @@ func (c *Cache) Add(id restic.ID, blob []byte) (old []byte) {
c.mu.Lock()
defer c.mu.Unlock()
var key interface{} = id
if c.c.Contains(key) { // Doesn't update the recency list.
if c.c.Contains(id) { // Doesn't update the recency list.
return
}
// This loop takes at most min(maxEntries, maxchunksize/overhead)
// iterations.
for size > c.free {
_, val, _ := c.c.RemoveOldest()
b := val.([]byte)
_, b, _ := c.c.RemoveOldest()
if cap(b) > cap(old) {
// We can only return one buffer, so pick the largest.
old = b
}
}
c.c.Add(key, blob)
c.c.Add(id, blob)
c.free -= size
return old
@ -80,17 +77,15 @@ func (c *Cache) Add(id restic.ID, blob []byte) (old []byte) {
func (c *Cache) Get(id restic.ID) ([]byte, bool) {
c.mu.Lock()
value, ok := c.c.Get(id)
blob, ok := c.c.Get(id)
c.mu.Unlock()
debug.Log("bloblru.Cache: get %v, hit %v", id, ok)
blob, ok := value.([]byte)
return blob, ok
}
func (c *Cache) evict(key, value interface{}) {
blob := value.([]byte)
func (c *Cache) evict(key restic.ID, blob []byte) {
debug.Log("bloblru.Cache: evict %v, %d bytes", key, cap(blob))
c.free += cap(blob) + overhead
}

View File

@ -1,6 +1,7 @@
package bloblru
import (
"math/rand"
"testing"
"github.com/restic/restic/internal/restic"
@ -50,3 +51,29 @@ func TestCache(t *testing.T) {
rtest.Equals(t, cacheSize, c.size)
rtest.Equals(t, cacheSize, c.free)
}
func BenchmarkAdd(b *testing.B) {
const (
MiB = 1 << 20
nblobs = 64
)
c := New(64 * MiB)
buf := make([]byte, 8*MiB)
ids := make([]restic.ID, nblobs)
sizes := make([]int, nblobs)
r := rand.New(rand.NewSource(100))
for i := range ids {
r.Read(ids[i][:])
sizes[i] = r.Intn(8 * MiB)
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
c.Add(ids[i%nblobs], buf[:sizes[i%nblobs]])
}
}