Skip to content

Commit 2c9c102

Browse files
committed
Replace ItemsToPrune with PercentToPrune
#97
1 parent f9779b4 commit 2c9c102

File tree

7 files changed

+219
-98
lines changed

7 files changed

+219
-98
lines changed

cache.go

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,27 @@ import (
1010
type Cache[T any] struct {
1111
*Configuration[T]
1212
control
13-
list *List[T]
14-
size int64
15-
buckets []*bucket[T]
16-
bucketMask uint32
17-
deletables chan *Item[T]
18-
promotables chan *Item[T]
13+
list *List[T]
14+
size int64
15+
pruneTargetSize int64
16+
buckets []*bucket[T]
17+
bucketMask uint32
18+
deletables chan *Item[T]
19+
promotables chan *Item[T]
1920
}
2021

2122
// Create a new cache with the specified configuration
2223
// See ccache.Configure() for creating a configuration
2324
func New[T any](config *Configuration[T]) *Cache[T] {
2425
c := &Cache[T]{
25-
list: NewList[T](),
26-
Configuration: config,
27-
control: newControl(),
28-
bucketMask: uint32(config.buckets) - 1,
29-
buckets: make([]*bucket[T], config.buckets),
30-
deletables: make(chan *Item[T], config.deleteBuffer),
31-
promotables: make(chan *Item[T], config.promoteBuffer),
26+
list: NewList[T](),
27+
Configuration: config,
28+
control: newControl(),
29+
bucketMask: uint32(config.buckets) - 1,
30+
buckets: make([]*bucket[T], config.buckets),
31+
deletables: make(chan *Item[T], config.deleteBuffer),
32+
promotables: make(chan *Item[T], config.promoteBuffer),
33+
pruneTargetSize: config.maxSize - config.maxSize*int64(config.percentToPrune)/100,
3234
}
3335
for i := 0; i < config.buckets; i++ {
3436
c.buckets[i] = &bucket[T]{
@@ -249,7 +251,9 @@ func (c *Cache[T]) worker() {
249251
msg.res <- dropped
250252
dropped = 0
251253
case controlSetMaxSize:
252-
c.maxSize = msg.size
254+
newMaxSize := msg.size
255+
c.maxSize = newMaxSize
256+
c.pruneTargetSize = newMaxSize - newMaxSize*int64(c.percentToPrune)/100
253257
if c.size > c.maxSize {
254258
dropped += c.gc()
255259
}
@@ -362,19 +366,21 @@ func (c *Cache[T]) gc() int {
362366
dropped := 0
363367
item := c.list.Tail
364368

365-
itemsToPrune := int64(c.itemsToPrune)
366-
if min := c.size - c.maxSize; min > itemsToPrune {
367-
itemsToPrune = min
368-
}
369+
prunedSize := int64(0)
370+
sizeToPrune := c.size - c.pruneTargetSize
369371

370-
for i := int64(0); i < itemsToPrune; i++ {
372+
for prunedSize < sizeToPrune {
371373
if item == nil {
372374
return dropped
373375
}
376+
// fmt.Println(item.key)
374377
prev := item.prev
375378
if !c.tracking || atomic.LoadInt32(&item.refCount) == 0 {
376379
c.bucket(item.key).delete(item.key)
377-
c.size -= item.size
380+
itemSize := item.size
381+
c.size -= itemSize
382+
prunedSize += itemSize
383+
378384
c.list.Remove(item)
379385
if c.onDelete != nil {
380386
c.onDelete(item)

cache_test.go

Lines changed: 70 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ func Test_CacheOnDeleteCallbackCalled(t *testing.T) {
135135
}
136136

137137
cache := New(Configure[string]().OnDelete(onDeleteFn))
138+
defer cache.Stop()
139+
138140
cache.Set("spice", "flow", time.Minute)
139141
cache.Set("worm", "sand", time.Minute)
140142

@@ -150,6 +152,8 @@ func Test_CacheOnDeleteCallbackCalled(t *testing.T) {
150152

151153
func Test_CacheFetchesExpiredItems(t *testing.T) {
152154
cache := New(Configure[string]())
155+
defer cache.Stop()
156+
153157
fn := func() (string, error) { return "moo-moo", nil }
154158

155159
cache.Set("beef", "moo", time.Second*-1)
@@ -160,20 +164,24 @@ func Test_CacheFetchesExpiredItems(t *testing.T) {
160164
}
161165

162166
func Test_CacheGCsTheOldestItems(t *testing.T) {
163-
cache := New(Configure[int]().ItemsToPrune(10))
164-
for i := 0; i < 500; i++ {
167+
cache := New(Configure[int]().MaxSize(100).PercentToPrune(10))
168+
defer cache.Stop()
169+
170+
for i := 0; i < 100; i++ {
165171
cache.Set(strconv.Itoa(i), i, time.Minute)
166172
}
167173
cache.SyncUpdates()
168174
cache.GC()
169175
assert.Equal(t, cache.Get("9"), nil)
170176
assert.Equal(t, cache.Get("10").Value(), 10)
171-
assert.Equal(t, cache.ItemCount(), 490)
177+
assert.Equal(t, cache.ItemCount(), 90)
172178
}
173179

174180
func Test_CachePromotedItemsDontGetPruned(t *testing.T) {
175-
cache := New(Configure[int]().ItemsToPrune(10).GetsPerPromote(1))
176-
for i := 0; i < 500; i++ {
181+
cache := New(Configure[int]().MaxSize(100).PercentToPrune(10).GetsPerPromote(1))
182+
defer cache.Stop()
183+
184+
for i := 0; i < 100; i++ {
177185
cache.Set(strconv.Itoa(i), i, time.Minute)
178186
}
179187
cache.SyncUpdates()
@@ -186,8 +194,10 @@ func Test_CachePromotedItemsDontGetPruned(t *testing.T) {
186194
}
187195

188196
func Test_GetWithoutPromoteDoesNotPromote(t *testing.T) {
189-
cache := New(Configure[int]().ItemsToPrune(10).GetsPerPromote(1))
190-
for i := 0; i < 500; i++ {
197+
cache := New(Configure[int]().MaxSize(100).PercentToPrune(10).GetsPerPromote(1))
198+
defer cache.Stop()
199+
200+
for i := 0; i < 100; i++ {
191201
cache.Set(strconv.Itoa(i), i, time.Minute)
192202
}
193203
cache.SyncUpdates()
@@ -200,18 +210,28 @@ func Test_GetWithoutPromoteDoesNotPromote(t *testing.T) {
200210
}
201211

202212
func Test_CacheTrackerDoesNotCleanupHeldInstance(t *testing.T) {
203-
cache := New(Configure[int]().ItemsToPrune(11).Track())
213+
cache := New(Configure[int]().MaxSize(10).PercentToPrune(10).Track())
214+
defer cache.Stop()
215+
204216
item0 := cache.TrackingSet("0", 0, time.Minute)
205-
for i := 1; i < 11; i++ {
217+
218+
cache.Set("1", 1, time.Minute)
219+
item1 := cache.TrackingGet("1")
220+
221+
for i := 2; i < 11; i++ {
206222
cache.Set(strconv.Itoa(i), i, time.Minute)
207223
}
208-
item1 := cache.TrackingGet("1")
224+
209225
cache.SyncUpdates()
210226
cache.GC()
211227
assert.Equal(t, cache.Get("0").Value(), 0)
212228
assert.Equal(t, cache.Get("1").Value(), 1)
213229
item0.Release()
214230
item1.Release()
231+
232+
for i := 1; i < 5; i++ {
233+
cache.Set(strconv.Itoa(i+20), i, time.Minute)
234+
}
215235
cache.GC()
216236
assert.Equal(t, cache.Get("0"), nil)
217237
assert.Equal(t, cache.Get("1"), nil)
@@ -225,7 +245,9 @@ func Test_CacheRemovesOldestItemWhenFull(t *testing.T) {
225245
}
226246
}
227247

228-
cache := New(Configure[int]().MaxSize(5).ItemsToPrune(1).OnDelete(onDeleteFn))
248+
cache := New(Configure[int]().MaxSize(5).PercentToPrune(1).OnDelete(onDeleteFn))
249+
defer cache.Stop()
250+
229251
for i := 0; i < 7; i++ {
230252
cache.Set(strconv.Itoa(i), i, time.Minute)
231253
}
@@ -238,11 +260,14 @@ func Test_CacheRemovesOldestItemWhenFull(t *testing.T) {
238260
}
239261

240262
func Test_CacheRemovesOldestItemWhenFullBySizer(t *testing.T) {
241-
cache := New(Configure[*SizedItem]().MaxSize(9).ItemsToPrune(2))
242-
for i := 0; i < 7; i++ {
263+
cache := New(Configure[*SizedItem]().MaxSize(50).PercentToPrune(15))
264+
defer cache.Stop()
265+
266+
for i := 0; i < 25; i++ {
243267
cache.Set(strconv.Itoa(i), &SizedItem{i, 2}, time.Minute)
244268
}
245269
cache.SyncUpdates()
270+
cache.GC()
246271
assert.Equal(t, cache.Get("0"), nil)
247272
assert.Equal(t, cache.Get("1"), nil)
248273
assert.Equal(t, cache.Get("2"), nil)
@@ -254,6 +279,8 @@ func Test_CacheRemovesOldestItemWhenFullBySizer(t *testing.T) {
254279

255280
func Test_CacheSetUpdatesSizeOnDelta(t *testing.T) {
256281
cache := New(Configure[*SizedItem]())
282+
defer cache.Stop()
283+
257284
cache.Set("a", &SizedItem{0, 2}, time.Minute)
258285
cache.Set("b", &SizedItem{0, 3}, time.Minute)
259286
cache.SyncUpdates()
@@ -274,6 +301,8 @@ func Test_CacheSetUpdatesSizeOnDelta(t *testing.T) {
274301

275302
func Test_CacheReplaceDoesNotchangeSizeIfNotSet(t *testing.T) {
276303
cache := New(Configure[*SizedItem]())
304+
defer cache.Stop()
305+
277306
cache.Set("1", &SizedItem{1, 2}, time.Minute)
278307
cache.Set("2", &SizedItem{1, 2}, time.Minute)
279308
cache.Set("3", &SizedItem{1, 2}, time.Minute)
@@ -284,6 +313,8 @@ func Test_CacheReplaceDoesNotchangeSizeIfNotSet(t *testing.T) {
284313

285314
func Test_CacheReplaceChangesSize(t *testing.T) {
286315
cache := New(Configure[*SizedItem]())
316+
defer cache.Stop()
317+
287318
cache.Set("1", &SizedItem{1, 2}, time.Minute)
288319
cache.Set("2", &SizedItem{1, 2}, time.Minute)
289320

@@ -301,39 +332,42 @@ func Test_CacheReplaceChangesSize(t *testing.T) {
301332
}
302333

303334
func Test_CacheResizeOnTheFly(t *testing.T) {
304-
cache := New(Configure[int]().MaxSize(9).ItemsToPrune(1))
305-
for i := 0; i < 5; i++ {
335+
cache := New(Configure[int]().MaxSize(50).PercentToPrune(10))
336+
defer cache.Stop()
337+
338+
for i := 0; i < 50; i++ {
306339
cache.Set(strconv.Itoa(i), i, time.Minute)
307340
}
308341
cache.SetMaxSize(3)
309342
cache.SyncUpdates()
310-
assert.Equal(t, cache.GetDropped(), 2)
311-
assert.Equal(t, cache.Get("0"), nil)
312-
assert.Equal(t, cache.Get("1"), nil)
313-
assert.Equal(t, cache.Get("2").Value(), 2)
314-
assert.Equal(t, cache.Get("3").Value(), 3)
315-
assert.Equal(t, cache.Get("4").Value(), 4)
343+
assert.Equal(t, cache.GetDropped(), 47)
344+
assert.Equal(t, cache.Get("46"), nil)
345+
assert.Equal(t, cache.Get("47").Value(), 47)
346+
assert.Equal(t, cache.Get("48").Value(), 48)
347+
assert.Equal(t, cache.Get("49").Value(), 49)
316348

317-
cache.Set("5", 5, time.Minute)
349+
cache.Set("50", 50, time.Minute)
318350
cache.SyncUpdates()
319351
assert.Equal(t, cache.GetDropped(), 1)
320-
assert.Equal(t, cache.Get("2"), nil)
321-
assert.Equal(t, cache.Get("3").Value(), 3)
322-
assert.Equal(t, cache.Get("4").Value(), 4)
323-
assert.Equal(t, cache.Get("5").Value(), 5)
352+
assert.Equal(t, cache.Get("47"), nil)
353+
assert.Equal(t, cache.Get("48").Value(), 48)
354+
assert.Equal(t, cache.Get("49").Value(), 49)
355+
assert.Equal(t, cache.Get("50").Value(), 50)
324356

325357
cache.SetMaxSize(10)
326-
cache.Set("6", 6, time.Minute)
358+
cache.Set("51", 51, time.Minute)
327359
cache.SyncUpdates()
328360
assert.Equal(t, cache.GetDropped(), 0)
329-
assert.Equal(t, cache.Get("3").Value(), 3)
330-
assert.Equal(t, cache.Get("4").Value(), 4)
331-
assert.Equal(t, cache.Get("5").Value(), 5)
332-
assert.Equal(t, cache.Get("6").Value(), 6)
361+
assert.Equal(t, cache.Get("48").Value(), 48)
362+
assert.Equal(t, cache.Get("49").Value(), 49)
363+
assert.Equal(t, cache.Get("50").Value(), 50)
364+
assert.Equal(t, cache.Get("51").Value(), 51)
333365
}
334366

335367
func Test_CacheForEachFunc(t *testing.T) {
336-
cache := New(Configure[int]().MaxSize(3).ItemsToPrune(1))
368+
cache := New(Configure[int]().MaxSize(3).PercentToPrune(1))
369+
defer cache.Stop()
370+
337371
assert.List(t, forEachKeys[int](cache), []string{})
338372

339373
cache.Set("1", 1, time.Minute)
@@ -362,7 +396,9 @@ func Test_CacheForEachFunc(t *testing.T) {
362396

363397
func Test_CachePrune(t *testing.T) {
364398
maxSize := int64(500)
365-
cache := New(Configure[string]().MaxSize(maxSize).ItemsToPrune(50))
399+
cache := New(Configure[string]().MaxSize(maxSize).PercentToPrune(50))
400+
defer cache.Stop()
401+
366402
epoch := 0
367403
for i := 0; i < 10000; i++ {
368404
epoch += 1
@@ -445,6 +481,7 @@ func Test_ConcurrentClearAndSet(t *testing.T) {
445481
}
446482
t.Errorf("cache list and lookup are not consistent")
447483
t.FailNow()
484+
cache.Stop()
448485
}
449486
}
450487

configuration.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ type Configuration[T any] struct {
44
maxSize int64
55
buckets int
66
itemsToPrune int
7+
percentToPrune int
78
deleteBuffer int
89
promoteBuffer int
910
getsPerPromote int32
@@ -17,7 +18,8 @@ type Configuration[T any] struct {
1718
func Configure[T any]() *Configuration[T] {
1819
return &Configuration[T]{
1920
buckets: 16,
20-
itemsToPrune: 500,
21+
itemsToPrune: 0,
22+
percentToPrune: 10,
2123
deleteBuffer: 1024,
2224
getsPerPromote: 3,
2325
promoteBuffer: 1024,
@@ -44,10 +46,13 @@ func (c *Configuration[T]) Buckets(count uint32) *Configuration[T] {
4446
return c
4547
}
4648

47-
// The number of items to prune when memory is low
48-
// [500]
49-
func (c *Configuration[T]) ItemsToPrune(count uint32) *Configuration[T] {
50-
c.itemsToPrune = int(count)
49+
// The percent of the max size to prune when memory is low.
50+
// [10]
51+
func (c *Configuration[T]) PercentToPrune(percent uint8) *Configuration[T] {
52+
if percent > 100 {
53+
percent = 20
54+
}
55+
c.percentToPrune = int(percent)
5156
return c
5257
}
5358

0 commit comments

Comments
 (0)