Skip to content

Commit

Permalink
remade lru, made faster get method, updated data structure
Browse files Browse the repository at this point in the history
  • Loading branch information
alserov committed Jul 24, 2024
1 parent 702730f commit f516c0f
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 145 deletions.
4 changes: 2 additions & 2 deletions lfu.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ type lfu struct {
pushCh chan *lfuNode
updatePosCh chan string

len uint64
limit uint64
len int
limit int

clearPeriod *time.Duration
}
Expand Down
160 changes: 41 additions & 119 deletions lru.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,15 @@ import (
)

func NewLRU(mods ...ModifierFunc) Cache {
ctx, cancel := context.WithCancel(context.Background())

c := lru{
limit: DefaultLimit,
vals: make(map[string]any),
pushCh: make(chan *lruNode, DefaultLimit),
popCh: make(chan struct{}, 10),
updatePosCh: make(chan string, 10),
stopCtx: ctx,
stopCtxCancel: cancel,
limit: DefaultLimit,
vals: make(map[string]*lruNode),
}

for _, mod := range mods {
mod(&c)
}

go c.pop()
go c.push()
go c.updatePos()

return &c
}

Expand All @@ -41,137 +30,70 @@ type lru struct {
head *lruNode
tail *lruNode

vals map[string]any
vals map[string]*lruNode

mu sync.RWMutex

stopCtx context.Context
stopCtxCancel context.CancelFunc

popCh chan struct{}
pushCh chan *lruNode
updatePosCh chan string

len uint64
limit uint64
limit int
}

func (c *lru) Close() {
c.stopCtxCancel()
clear(c.vals)
c.head = nil
c.tail = nil
}

// Get retrieves value by its id, returns false if not found
func (c *lru) Get(ctx context.Context, key string) (any, bool) {
c.mu.RLock()
val, ok := c.vals[key]
c.mu.RUnlock()

if ok {
select {
case c.updatePosCh <- key:
case <-ctx.Done():
close(c.updatePosCh)
}
if !ok {
return nil, false
}

return val, ok
}

func (c *lru) Set(ctx context.Context, key string, val any) {
c.mu.Lock()
_, ok := c.vals[key]
if ok {
c.mu.Unlock()
return
}
c.vals[key] = val
c.mu.Unlock()

select {
case c.pushCh <- newLRUNode(key, val):
case <-c.stopCtx.Done():
close(c.pushCh)
}
}

func (c *lru) pop() {
deleteAtEnd := func() {
c.mu.Lock()
defer c.mu.Unlock()

delete(c.vals, c.tail.key)
c.tail = c.tail.prev
c.tail.next = nil
}

for range c.popCh {
deleteAtEnd()
}
}

func (c *lru) push() {
defer func() {
close(c.popCh)
}()

insertAtStart := func(n *lruNode) {
c.mu.Lock()
defer c.mu.Unlock()
if len(c.vals) == 2 {
c.head, c.tail = c.tail, c.head
c.head.next = c.tail
c.tail.prev = c.head
} else {
if val.prev != nil {
c.vals[key].prev.next = c.vals[key].next
}

if c.head == nil {
c.head = n
c.tail = n
if val.next != nil {
c.vals[key].next.prev = c.vals[key].prev
} else {
if c.len >= c.limit {
c.popCh <- struct{}{}
if c.tail != nil {
c.tail = c.tail.prev
}
n.next = c.head
c.head.prev = n
c.head = n
}

c.len++
}
c.mu.RUnlock()

for n := range c.pushCh {
insertAtStart(n)
}
return val.val, true
}

func (c *lru) updatePos() {
removeAndInsertAtStart := func(key string) {
c.mu.Lock()
defer c.mu.Unlock()

curr := c.head

for curr != nil {
if curr.key == key && curr.prev != nil {
if curr.next != nil {
curr.next.prev = curr.prev
} else {
c.tail = curr.prev
}

curr.prev.next = curr.next
func (c *lru) Set(ctx context.Context, key string, val any) {
c.mu.Lock()
node := &lruNode{
prev: nil,
next: c.head,
key: key,
val: val,
}

curr.prev = nil
curr.next = c.head
c.head.prev = curr
c.head = curr
if len(c.vals) == 0 {
c.tail = node
}

break
}
c.vals[key] = node
c.head = c.vals[key]

curr = curr.next
if len(c.vals) == c.limit {
if c.tail != nil {
delete(c.vals, c.tail.key)
c.tail = c.tail.prev
}
}

for key := range c.updatePosCh {
removeAndInsertAtStart(key)
}
}

func newLRUNode(key string, val any) *lruNode {
return &lruNode{val: val, key: key}
c.mu.Unlock()
}
25 changes: 4 additions & 21 deletions lru_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"github.com/stretchr/testify/suite"
"math"
"testing"
"time"
)

func TestSuiteLRU(t *testing.T) {
Expand Down Expand Up @@ -116,19 +115,14 @@ func (s *SuiteLRU) TestExtrusion() {
{
key: "10",
},
{
key: "11",
},
}

for _, tc := range tests {
c.Set(context.Background(), tc.key, nil)
}

// giving time to delete least recently used value
time.Sleep(time.Millisecond * 10)

val, ok := c.Get(context.Background(), tests[0].key)

require.False(s.T(), ok)
require.Nil(s.T(), val)
}
Expand All @@ -139,30 +133,19 @@ func (s *SuiteLRU) TestWithLimitMod() {
c.Set(context.Background(), "key", 1)
c.Set(context.Background(), "key1", 2)
c.Set(context.Background(), "key2", 3)
c.Set(context.Background(), "key3", 4)

// giving time to delete least recently used value
time.Sleep(time.Millisecond * 10)

val, ok := c.Get(context.Background(), "key")
require.False(s.T(), ok)
require.Nil(s.T(), val)
}

func (s *SuiteLRU) TestReplace() {
c := New(LRU, WithLimit(2))
c := New(LRU, WithLimit(3))

c.Set(context.Background(), "key", 1)
c.Set(context.Background(), "key1", 2)

time.Sleep(time.Millisecond * 10)
c.Set(context.Background(), "key1", 1)
c.Get(context.Background(), "key")

time.Sleep(time.Millisecond * 10)

c.Set(context.Background(), "key2", 2)

time.Sleep(time.Millisecond * 150)
c.Set(context.Background(), "key2", 1)

val, ok := c.Get(context.Background(), "key")
require.True(s.T(), ok)
Expand Down
2 changes: 1 addition & 1 deletion modifiers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import "time"

// Modifiers

func WithLimit(lim uint64) ModifierFunc {
func WithLimit(lim int) ModifierFunc {
return func(cache any) {
switch cache.(type) {
case *lru:
Expand Down
4 changes: 2 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@ func main() {
```text
cpu: Intel(R) Core(TM) i5-10400F CPU @ 2.90GHz
BenchmarkLRUGet
BenchmarkLRUGet-12 81670431 14.31 ns/op
BenchmarkLRUGet-12 137647732 8.623 ns/op
```


#### Set
```text
cpu: Intel(R) Core(TM) i5-10400F CPU @ 2.90GHz
BenchmarkLRUSet
BenchmarkLRUSet-12 44247624 27.89 ns/op
BenchmarkLRUSet-12 11138575 97.77 ns/op
```

### LFU
Expand Down

0 comments on commit f516c0f

Please sign in to comment.