Skip to content

Commit

Permalink
feat(lru): lru cache v2
Browse files Browse the repository at this point in the history
  • Loading branch information
Ccheers committed May 17, 2024
1 parent 89c3ab3 commit 2e20a9f
Show file tree
Hide file tree
Showing 5 changed files with 688 additions and 77 deletions.
95 changes: 18 additions & 77 deletions lru/lru.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,118 +5,59 @@ import (
"sync"
"time"

"github.com/ccheers/xpkg/generic/containerx/heap"
v2 "github.com/ccheers/xpkg/lru/v2"
)

type ILRUCache interface {
Set(ctx context.Context, key string, value interface{}, expireAt time.Time)
Get(ctx context.Context, key string) (interface{}, bool)
}

type node struct {
expireAt time.Time
key string
value interface{}
}

type T struct {
heap *heap.Heap[*node]
objPool sync.Pool

arcCache *v2.ARCCache
latestGCAt time.Time

gcLock sync.Mutex

maxLen int

mu sync.RWMutex
mm map[string]*node
mu sync.Mutex
mm map[string]time.Time
}

func NewLRUCache(maxLen int) ILRUCache {
cache, _ := v2.NewARC(int(uint32(maxLen)))
return &T{
heap: heap.New[*node](func(a, b *node) bool {
return a.expireAt.After(b.expireAt)
}),
objPool: sync.Pool{
New: func() any {
return &node{}
},
},
maxLen: maxLen,
mm: make(map[string]*node, maxLen),
arcCache: cache,
latestGCAt: time.Unix(0, 0),
mm: make(map[string]time.Time, maxLen),
}
}

func (x *T) Set(ctx context.Context, key string, value interface{}, expireAt time.Time) {
defer x.gcTick()

x.mu.Lock()
node := x.objPool.Get().(*node)
node.expireAt = expireAt
node.key = key
node.value = value

x.heap.Push(node)
x.mm[key] = node

if len(x.mm) > x.maxLen {
node, ok := x.heap.Pop()
if ok {
delete(x.mm, node.key)
}
x.objPool.Put(node)
}
x.mm[key] = expireAt
x.mu.Unlock()
x.arcCache.Add(key, value)
}

func (x *T) Get(ctx context.Context, key string) (interface{}, bool) {
defer x.gcTick()

x.mu.RLock()
node, ok := x.mm[key]
x.mu.RUnlock()
if !ok {
return nil, false
}
if node.expireAt.Before(time.Now()) {
return nil, false
}
return node.value, ok
x.gcTick()
return x.arcCache.Get(key)
}

func (x *T) gcTick() {
const calmDuration = time.Second * 10
if !x.gcLock.TryLock() {
if !x.mu.TryLock() {
return
}
defer x.gcLock.Unlock()
defer x.mu.Unlock()

now := time.Now()
if now.Sub(x.latestGCAt) < calmDuration {
return
}
x.mu.Lock()
for {
node, ok := x.heap.Pop()
if !ok {
break
}
if node.expireAt.Before(now) {
delete(x.mm, node.key)
node.value = nil
x.objPool.Put(node)
} else {
x.heap.Push(node)
break
for key, t := range x.mm {
if t.Before(now) {
x.arcCache.Remove(key)
delete(x.mm, key)
}
}
x.latestGCAt = now
mm := make(map[string]*node, len(x.mm))
// 缩小 bucket 空隙
for k, v := range x.mm {
mm[k] = v
}
x.mm = mm
x.mu.Unlock()
}
33 changes: 33 additions & 0 deletions lru/lru_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,36 @@ func TestNewLRUCache(t *testing.T) {
t.Fatal("should be empty")
}
}

func BenchmarkLRUCache(b *testing.B) {
for i := 0; i < b.N; i++ {
cache := NewLRUCache(3)
ctx := context.TODO()
cache.Set(ctx, "1", 1, time.Now().Add(time.Second))
cache.Set(ctx, "2", 2, time.Now().Add(time.Second))
cache.Set(ctx, "3", 3, time.Now().Add(time.Second))
cache.Set(ctx, "4", 4, time.Now().Add(time.Second))
cache.Set(ctx, "5", 5, time.Now().Add(time.Second))
cache.Set(ctx, "6", 6, time.Now().Add(time.Second))
cache.Set(ctx, "7", 7, time.Now().Add(time.Second))
cache.Set(ctx, "8", 8, time.Now().Add(time.Second))
cache.Set(ctx, "9", 9, time.Now().Add(time.Second))
cache.Set(ctx, "10", 10, time.Now().Add(time.Second))
cache.Set(ctx, "11", 11, time.Now().Add(time.Second))
cache.Set(ctx, "12", 12, time.Now().Add(time.Second))
cache.Set(ctx, "13", 13, time.Now().Add(time.Second))
cache.Set(ctx, "14", 14, time.Now().Add(time.Second))
cache.Set(ctx, "15", 15, time.Now().Add(time.Second))
cache.Set(ctx, "16", 16, time.Now().Add(time.Second))
cache.Set(ctx, "17", 17, time.Now().Add(time.Second))
cache.Set(ctx, "18", 18, time.Now().Add(time.Second))
cache.Set(ctx, "19", 19, time.Now().Add(time.Second))
cache.Set(ctx, "20", 20, time.Now().Add(time.Second))
cache.Set(ctx, "21", 21, time.Now().Add(time.Second))
cache.Set(ctx, "22", 22, time.Now().Add(time.Second))
cache.Set(ctx, "23", 23, time.Now().Add(time.Second))
cache.Set(ctx, "24", 24, time.Now().Add(time.Second))
cache.Set(ctx, "25", 25, time.Now().Add(time.Second))
cache.Set(ctx, "26", 26, time.Now().Add(time.Second))
}
}
Loading

0 comments on commit 2e20a9f

Please sign in to comment.