Skip to content

Commit

Permalink
add EstimatedSize method on cache
Browse files Browse the repository at this point in the history
  • Loading branch information
nlachfr committed Aug 19, 2024
1 parent 038e932 commit 1fb8827
Show file tree
Hide file tree
Showing 9 changed files with 114 additions and 50 deletions.
10 changes: 10 additions & 0 deletions cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ func (c *Cache[K, V]) Size() int {
return c.store.Size()
}

// EstimatedSize returns an approximate used size of the cache.
func (c *Cache[K, V]) EstimatedSize() int {
return c.store.EstimatedSize()
}

// Close closes all goroutines created by cache.
func (c *Cache[K, V]) Close() {
c.store.Close()
Expand Down Expand Up @@ -136,6 +141,11 @@ func (c *LoadingCache[K, V]) Size() int {
return c.store.Size()
}

// EstimatedSize returns an approximate used size of the cache.
func (c *LoadingCache[K, V]) EstimatedSize() int {
return c.store.EstimatedSize()
}

// SaveCache save cache data to writer.
func (c *LoadingCache[K, V]) SaveCache(version uint64, writer io.Writer) error {
return c.store.Persist(version, writer)
Expand Down
39 changes: 39 additions & 0 deletions cache_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package theine_test

import (
"context"
"fmt"
"math/rand"
"strconv"
Expand All @@ -11,6 +12,7 @@ import (

"github.com/Yiling-J/theine-go"
"github.com/stretchr/testify/require"
"golang.org/x/sync/errgroup"
)

func TestMaxsizeZero(t *testing.T) {
Expand Down Expand Up @@ -227,6 +229,7 @@ func TestCost(t *testing.T) {
time.Sleep(time.Second)
require.True(t, client.Len() == 25)
require.True(t, client.Size() == 500)
require.True(t, client.EstimatedSize() == 500)

// test cost func
builder := theine.NewBuilder[string, string](500)
Expand All @@ -245,6 +248,7 @@ func TestCost(t *testing.T) {
time.Sleep(time.Second)
require.True(t, client.Len() == 25)
require.True(t, client.Size() == 500)
require.True(t, client.EstimatedSize() == 500)
client.Close()
}

Expand All @@ -259,16 +263,51 @@ func TestCostUpdate(t *testing.T) {
time.Sleep(time.Second)
require.True(t, client.Len() == 25)
require.True(t, client.Size() == 500)
require.True(t, client.EstimatedSize() == 500)
// update cost
success := client.Set("key:10", "", 200)
require.True(t, success)
time.Sleep(time.Second)
// 15 * 20 + 200
require.True(t, client.Len() == 16)
require.True(t, client.Size() == 15*20+200)
require.True(t, client.EstimatedSize() == 15*20+200)
client.Close()
}

func TestEstimatedSize(t *testing.T) {
client, err := theine.NewBuilder[int, int](500).Build()
require.Nil(t, err)
ctx, cfn := context.WithCancel(context.Background())
defer cfn()
wg, ctx := errgroup.WithContext(ctx)
wg.Go(func() error {
tkr := time.NewTicker(time.Nanosecond)
defer tkr.Stop()
for {
select {
case <-tkr.C:
client.EstimatedSize()
case <-ctx.Done():
return nil
}
}
})
wg.Go(func() error {
defer cfn()
for i := 0; i < 10000000; i++ {
if i%2 == 0 {
client.Set(i, 1, 1)
} else {
client.Get(i)
}
}
return nil
})
require.Nil(t, wg.Wait())
require.True(t, client.EstimatedSize() == client.Size())
}

func TestDoorkeeper(t *testing.T) {
builder := theine.NewBuilder[string, string](500)
builder.Doorkeeper(true)
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
github.com/gammazero/deque v0.2.1
github.com/stretchr/testify v1.8.2
github.com/zeebo/xxh3 v1.0.2
golang.org/x/sync v0.8.0
golang.org/x/sys v0.8.0
)

Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
20 changes: 11 additions & 9 deletions internal/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io"
"strings"
"sync/atomic"
)

const (
Expand All @@ -17,9 +18,9 @@ const (
// List represents a doubly linked list.
// The zero value for List is an empty list ready to use.
type List[K comparable, V any] struct {
root Entry[K, V] // sentinel list element, only &root, root.prev, and root.next are used
len int // current list length(sum of costs) excluding (this) sentinel element
count int // count of entries in list
root Entry[K, V] // sentinel list element, only &root, root.prev, and root.next are used
len atomic.Int64 // current list length(sum of costs) excluding (this) sentinel element
count int // count of entries in list
capacity uint
bounded bool
listType uint8 // 1 tinylfu list, 2 timerwheel list
Expand All @@ -31,7 +32,7 @@ func NewList[K comparable, V any](size uint, listType uint8) *List[K, V] {
l.root.root = true
l.root.setNext(&l.root, l.listType)
l.root.setPrev(&l.root, l.listType)
l.len = 0
l.len = atomic.Int64{}
l.capacity = size
if size > 0 {
l.bounded = true
Expand All @@ -42,12 +43,12 @@ func NewList[K comparable, V any](size uint, listType uint8) *List[K, V] {
func (l *List[K, V]) Reset() {
l.root.setNext(&l.root, l.listType)
l.root.setPrev(&l.root, l.listType)
l.len = 0
l.len.Store(0)
}

// Len returns the number of elements of list l.
// The complexity is O(1).
func (l *List[K, V]) Len() int { return l.len }
func (l *List[K, V]) Len() int { return int(l.len.Load()) }

func (l *List[K, V]) display() string {
var s []string
Expand Down Expand Up @@ -86,7 +87,7 @@ func (l *List[K, V]) Back() *Entry[K, V] {
// insert inserts e after at, increments l.len, and evicted entry if capacity exceed
func (l *List[K, V]) insert(e, at *Entry[K, V]) *Entry[K, V] {
var evicted *Entry[K, V]
if l.bounded && l.len >= int(l.capacity) {
if l.bounded && l.len.Load() >= int64(l.capacity) {
evicted = l.PopTail()
}
if l.listType != WHEEL_LIST {
Expand All @@ -97,7 +98,8 @@ func (l *List[K, V]) insert(e, at *Entry[K, V]) *Entry[K, V] {
e.prev(l.listType).setNext(e, l.listType)
e.next(l.listType).setPrev(e, l.listType)
if l.bounded {
l.len += int(e.cost.Load())
l.len.Add(e.cost.Load())
// l.len += int(e.cost.Load())
l.count += 1
}
return evicted
Expand All @@ -123,7 +125,7 @@ func (l *List[K, V]) remove(e *Entry[K, V]) {
e.list = 0
}
if l.bounded {
l.len -= int(e.cost.Load())
l.len.Add(-e.cost.Load())
l.count -= 1
}
}
Expand Down
12 changes: 6 additions & 6 deletions internal/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ func TestList(t *testing.T) {
evicted := l.PushFront(NewEntry(fmt.Sprintf("%d", i), "", 1, 0))
require.Nil(t, evicted)
}
require.Equal(t, 5, l.len)
require.Equal(t, 5, int(l.len.Load()))
require.Equal(t, "4/3/2/1/0", l.display())
require.Equal(t, "0/1/2/3/4", l.displayReverse())

evicted := l.PushFront(NewEntry("5", "", 1, 0))
require.Equal(t, "0", evicted.key)
require.Equal(t, 5, l.len)
require.Equal(t, 5, int(l.len.Load()))
require.Equal(t, "5/4/3/2/1", l.display())
require.Equal(t, "1/2/3/4/5", l.displayReverse())

Expand Down Expand Up @@ -63,13 +63,13 @@ func TestListCountCost(t *testing.T) {
evicted := l.PushFront(NewEntry(fmt.Sprintf("%d", i), "", 20, 0))
require.Nil(t, evicted)
}
require.Equal(t, 100, l.len)
require.Equal(t, 100, int(l.len.Load()))
require.Equal(t, 5, l.count)
for i := 0; i < 3; i++ {
entry := l.PopTail()
require.NotNil(t, entry)
}
require.Equal(t, 40, l.len)
require.Equal(t, 40, int(l.len.Load()))
require.Equal(t, 2, l.count)
}

Expand All @@ -81,13 +81,13 @@ func TestWheelList(t *testing.T) {
evicted := l.PushFront(NewEntry(fmt.Sprintf("%d", i), "", 1, 0))
require.Nil(t, evicted)
}
require.Equal(t, 5, l.len)
require.Equal(t, 5, int(l.len.Load()))
require.Equal(t, "4/3/2/1/0", l.display())
require.Equal(t, "0/1/2/3/4", l.displayReverse())

evicted := l.PushFront(NewEntry("5", "", 1, 0))
require.Equal(t, "0", evicted.key)
require.Equal(t, 5, l.len)
require.Equal(t, 5, int(l.len.Load()))
require.Equal(t, "5/4/3/2/1", l.display())
require.Equal(t, "1/2/3/4/5", l.displayReverse())

Expand Down
4 changes: 2 additions & 2 deletions internal/slru.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ func (s *Slru[K, V]) remove(entry *Entry[K, V]) {
func (s *Slru[K, V]) updateCost(entry *Entry[K, V], delta int64) {
switch entry.list {
case LIST_PROBATION:
s.probation.len += int(delta)
s.probation.len.Add(delta)
case LIST_PROTECTED:
s.protected.len += int(delta)
s.protected.len.Add(delta)
}
}
14 changes: 12 additions & 2 deletions internal/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,16 @@ func (s *Store[K, V]) Size() int {
return total
}

func (s *Store[K, V]) EstimatedSize() int {
total := s.policy.slru.protected.Len() + s.policy.slru.probation.Len()
for _, s := range s.shards {
tk := s.mu.RLock()
total += s.qlen
s.mu.RUnlock(tk)
}
return total
}

// spread hash before get index
func (s *Store[K, V]) index(key K) (uint64, int) {
base := s.hasher.hash(key)
Expand Down Expand Up @@ -842,7 +852,7 @@ func (s *Store[K, V]) Recover(version uint64, reader io.Reader) error {
continue
}
l := s.policy.slru.protected
if l.len < int(l.capacity) {
if l.len.Load() < int64(l.capacity) {
entry := pentry.entry()
l.PushBack(entry)
s.insertSimple(entry)
Expand All @@ -865,7 +875,7 @@ func (s *Store[K, V]) Recover(version uint64, reader io.Reader) error {
}
l1 := s.policy.slru.protected
l2 := s.policy.slru.probation
if l1.len+l2.len < int(s.policy.slru.maxsize) {
if l1.len.Load()+l2.len.Load() < int64(s.policy.slru.maxsize) {
entry := pentry.entry()
l2.PushBack(entry)
s.insertSimple(entry)
Expand Down
Loading

0 comments on commit 1fb8827

Please sign in to comment.