Skip to content

Commit 61f5066

Browse files
committed
change to an intrinsic linked list for less memory usage
1 parent 0901f94 commit 61f5066

File tree

8 files changed

+102
-103
lines changed

8 files changed

+102
-103
lines changed

bucket.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,14 +93,20 @@ func (b *bucket[T]) set(key string, value T, duration time.Duration, track bool)
9393
return item, existing
9494
}
9595

96-
func (b *bucket[T]) delete(key string) *Item[T] {
96+
func (b *bucket[T]) remove(key string) *Item[T] {
9797
b.Lock()
9898
item := b.lookup[key]
9999
delete(b.lookup, key)
100100
b.Unlock()
101101
return item
102102
}
103103

104+
func (b *bucket[T]) delete(key string) {
105+
b.Lock()
106+
delete(b.lookup, key)
107+
b.Unlock()
108+
}
109+
104110
// This is an expensive operation, so we do what we can to optimize it and limit
105111
// the impact it has on concurrent operations. Specifically, we:
106112
// 1 - Do an initial iteration to collect matches. This allows us to do the

cache.go

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
type Cache[T any] struct {
1111
*Configuration[T]
1212
control
13-
list *List[*Item[T]]
13+
list *List[T]
1414
size int64
1515
buckets []*bucket[T]
1616
bucketMask uint32
@@ -22,7 +22,7 @@ type Cache[T any] struct {
2222
// See ccache.Configure() for creating a configuration
2323
func New[T any](config *Configuration[T]) *Cache[T] {
2424
c := &Cache[T]{
25-
list: NewList[*Item[T]](),
25+
list: NewList[T](),
2626
Configuration: config,
2727
control: newControl(),
2828
bucketMask: uint32(config.buckets) - 1,
@@ -184,7 +184,7 @@ func (c *Cache[T]) Fetch(key string, duration time.Duration, fetch func() (T, er
184184

185185
// Remove the item from the cache, return true if the item was present, false otherwise.
186186
func (c *Cache[T]) Delete(key string) bool {
187-
item := c.bucket(key).delete(key)
187+
item := c.bucket(key).remove(key)
188188
if item != nil {
189189
c.deletables <- item
190190
return true
@@ -269,7 +269,7 @@ func (c *Cache[T]) worker() {
269269
bucket.clear()
270270
}
271271
c.size = 0
272-
c.list = NewList[*Item[T]]()
272+
c.list = NewList[T]()
273273
})
274274
msg.done <- struct{}{}
275275
case controlGetSize:
@@ -327,15 +327,14 @@ doAllDeletes:
327327
}
328328

329329
func (c *Cache[T]) doDelete(item *Item[T]) {
330-
if item.node == nil {
330+
if item.next == nil && item.prev == nil {
331331
item.promotions = -2
332332
} else {
333333
c.size -= item.size
334334
if c.onDelete != nil {
335335
c.onDelete(item)
336336
}
337-
c.list.Remove(item.node)
338-
item.node = nil
337+
c.list.Remove(item)
339338
item.promotions = -2
340339
}
341340
}
@@ -345,46 +344,45 @@ func (c *Cache[T]) doPromote(item *Item[T]) bool {
345344
if item.promotions == -2 {
346345
return false
347346
}
348-
if item.node != nil { //not a new item
347+
348+
if item.next != nil || item.prev != nil { // not a new item
349349
if item.shouldPromote(c.getsPerPromote) {
350-
c.list.MoveToFront(item.node)
350+
c.list.MoveToFront(item)
351351
item.promotions = 0
352352
}
353353
return false
354354
}
355355

356356
c.size += item.size
357-
item.node = c.list.Insert(item)
357+
c.list.Insert(item)
358358
return true
359359
}
360360

361361
func (c *Cache[T]) gc() int {
362362
dropped := 0
363-
node := c.list.Tail
363+
item := c.list.Tail
364364

365365
itemsToPrune := int64(c.itemsToPrune)
366366
if min := c.size - c.maxSize; min > itemsToPrune {
367367
itemsToPrune = min
368368
}
369369

370370
for i := int64(0); i < itemsToPrune; i++ {
371-
if node == nil {
371+
if item == nil {
372372
return dropped
373373
}
374-
prev := node.Prev
375-
item := node.Value
374+
prev := item.prev
376375
if !c.tracking || atomic.LoadInt32(&item.refCount) == 0 {
377-
c.bucket(item.key).delete(item.key)
376+
c.bucket(item.key).remove(item.key)
378377
c.size -= item.size
379-
c.list.Remove(node)
378+
c.list.Remove(item)
380379
if c.onDelete != nil {
381380
c.onDelete(item)
382381
}
383382
dropped += 1
384-
item.node = nil
385383
item.promotions = -2
386384
}
387-
node = prev
385+
item = prev
388386
}
389387
return dropped
390388
}

item.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ type Item[T any] struct {
2727
expires int64
2828
size int64
2929
value T
30-
node *Node[*Item[T]]
30+
next *Item[T]
31+
prev *Item[T]
3132
}
3233

3334
func newItem[T any](key string, value T, expires int64, track bool) *Item[T] {
@@ -37,6 +38,7 @@ func newItem[T any](key string, value T, expires int64, track bool) *Item[T] {
3738
if sized, ok := (interface{})(value).(Sized); ok {
3839
size = sized.Size()
3940
}
41+
4042
item := &Item[T]{
4143
key: key,
4244
value: value,
@@ -97,5 +99,9 @@ func (i *Item[T]) Extend(duration time.Duration) {
9799
// fmt.Sprintf expression could cause fields of the Item to be read in a non-thread-safe
98100
// way.
99101
func (i *Item[T]) String() string {
100-
return fmt.Sprintf("Item(%v)", i.value)
102+
group := i.group
103+
if group == "" {
104+
return fmt.Sprintf("Item(%s:%v)", i.key, i.value)
105+
}
106+
return fmt.Sprintf("Item(%s:%s:%v)", group, i.key, i.value)
101107
}

layeredbucket.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,24 @@ func (b *layeredBucket[T]) set(primary, secondary string, value T, duration time
5151
return item, existing
5252
}
5353

54-
func (b *layeredBucket[T]) delete(primary, secondary string) *Item[T] {
54+
func (b *layeredBucket[T]) remove(primary, secondary string) *Item[T] {
5555
b.RLock()
5656
bucket, exists := b.buckets[primary]
5757
b.RUnlock()
5858
if !exists {
5959
return nil
6060
}
61-
return bucket.delete(secondary)
61+
return bucket.remove(secondary)
62+
}
63+
64+
func (b *layeredBucket[T]) delete(primary, secondary string) {
65+
b.RLock()
66+
bucket, exists := b.buckets[primary]
67+
b.RUnlock()
68+
if !exists {
69+
return
70+
}
71+
bucket.delete(secondary)
6272
}
6373

6474
func (b *layeredBucket[T]) deletePrefix(primary, prefix string, deletables chan *Item[T]) int {

layeredcache.go

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
type LayeredCache[T any] struct {
1111
*Configuration[T]
1212
control
13-
list *List[*Item[T]]
13+
list *List[T]
1414
buckets []*layeredBucket[T]
1515
bucketMask uint32
1616
size int64
@@ -33,7 +33,7 @@ type LayeredCache[T any] struct {
3333
// See ccache.Configure() for creating a configuration
3434
func Layered[T any](config *Configuration[T]) *LayeredCache[T] {
3535
c := &LayeredCache[T]{
36-
list: NewList[*Item[T]](),
36+
list: NewList[T](),
3737
Configuration: config,
3838
control: newControl(),
3939
bucketMask: uint32(config.buckets) - 1,
@@ -158,7 +158,7 @@ func (c *LayeredCache[T]) Fetch(primary, secondary string, duration time.Duratio
158158

159159
// Remove the item from the cache, return true if the item was present, false otherwise.
160160
func (c *LayeredCache[T]) Delete(primary, secondary string) bool {
161-
item := c.bucket(primary).delete(primary, secondary)
161+
item := c.bucket(primary).remove(primary, secondary)
162162
if item != nil {
163163
c.deletables <- item
164164
return true
@@ -262,7 +262,7 @@ func (c *LayeredCache[T]) worker() {
262262
bucket.clear()
263263
}
264264
c.size = 0
265-
c.list = NewList[*Item[T]]()
265+
c.list = NewList[T]()
266266
})
267267
msg.done <- struct{}{}
268268
case controlGetSize:
@@ -289,15 +289,14 @@ drain:
289289
}
290290

291291
func (c *LayeredCache[T]) doDelete(item *Item[T]) {
292-
if item.node == nil {
292+
if item.prev == nil && item.next == nil {
293293
item.promotions = -2
294294
} else {
295295
c.size -= item.size
296296
if c.onDelete != nil {
297297
c.onDelete(item)
298298
}
299-
c.list.Remove(item.node)
300-
item.node = nil
299+
c.list.Remove(item)
301300
item.promotions = -2
302301
}
303302
}
@@ -307,45 +306,45 @@ func (c *LayeredCache[T]) doPromote(item *Item[T]) bool {
307306
if item.promotions == -2 {
308307
return false
309308
}
310-
if item.node != nil { //not a new item
309+
310+
if item.next != nil || item.prev != nil { // not a new item
311311
if item.shouldPromote(c.getsPerPromote) {
312-
c.list.MoveToFront(item.node)
312+
c.list.MoveToFront(item)
313313
item.promotions = 0
314314
}
315315
return false
316316
}
317+
317318
c.size += item.size
318-
item.node = c.list.Insert(item)
319+
c.list.Insert(item)
319320
return true
320321
}
321322

322323
func (c *LayeredCache[T]) gc() int {
323-
node := c.list.Tail
324324
dropped := 0
325-
itemsToPrune := int64(c.itemsToPrune)
325+
item := c.list.Tail
326326

327+
itemsToPrune := int64(c.itemsToPrune)
327328
if min := c.size - c.maxSize; min > itemsToPrune {
328329
itemsToPrune = min
329330
}
330331

331332
for i := int64(0); i < itemsToPrune; i++ {
332-
if node == nil {
333+
if item == nil {
333334
return dropped
334335
}
335-
prev := node.Prev
336-
item := node.Value
336+
prev := item.prev
337337
if !c.tracking || atomic.LoadInt32(&item.refCount) == 0 {
338-
c.bucket(item.group).delete(item.group, item.key)
338+
c.bucket(item.group).remove(item.group, item.key)
339339
c.size -= item.size
340-
c.list.Remove(node)
340+
c.list.Remove(item)
341341
if c.onDelete != nil {
342342
c.onDelete(item)
343343
}
344-
item.node = nil
345-
item.promotions = -2
346344
dropped += 1
345+
item.promotions = -2
347346
}
348-
node = prev
347+
item = prev
349348
}
350349
return dropped
351350
}

list.go

Lines changed: 19 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,45 @@
11
package ccache
22

33
type List[T any] struct {
4-
Head *Node[T]
5-
Tail *Node[T]
4+
Head *Item[T]
5+
Tail *Item[T]
66
}
77

88
func NewList[T any]() *List[T] {
99
return &List[T]{}
1010
}
1111

12-
func (l *List[T]) Remove(node *Node[T]) {
13-
next := node.Next
14-
prev := node.Prev
12+
func (l *List[T]) Remove(item *Item[T]) {
13+
next := item.next
14+
prev := item.prev
1515

1616
if next == nil {
17-
l.Tail = node.Prev
17+
l.Tail = prev
1818
} else {
19-
next.Prev = prev
19+
next.prev = prev
2020
}
2121

2222
if prev == nil {
23-
l.Head = node.Next
23+
l.Head = next
2424
} else {
25-
prev.Next = next
25+
prev.next = next
2626
}
27-
node.Next = nil
28-
node.Prev = nil
27+
item.next = nil
28+
item.prev = nil
2929
}
3030

31-
func (l *List[T]) MoveToFront(node *Node[T]) {
32-
l.Remove(node)
33-
l.nodeToFront(node)
31+
func (l *List[T]) MoveToFront(item *Item[T]) {
32+
l.Remove(item)
33+
l.Insert(item)
3434
}
3535

36-
func (l *List[T]) Insert(value T) *Node[T] {
37-
node := &Node[T]{Value: value}
38-
l.nodeToFront(node)
39-
return node
40-
}
41-
42-
func (l *List[T]) nodeToFront(node *Node[T]) {
36+
func (l *List[T]) Insert(item *Item[T]) {
4337
head := l.Head
44-
l.Head = node
38+
l.Head = item
4539
if head == nil {
46-
l.Tail = node
40+
l.Tail = item
4741
return
4842
}
49-
node.Next = head
50-
head.Prev = node
51-
}
52-
53-
type Node[T any] struct {
54-
Next *Node[T]
55-
Prev *Node[T]
56-
Value T
43+
item.next = head
44+
head.prev = item
5745
}

0 commit comments

Comments
 (0)