Skip to content

Commit 6232c4f

Browse files
Thomas StrombergThomas Stromberg
authored andcommitted
perf tuning
1 parent 1af7798 commit 6232c4f

File tree

2 files changed

+24
-18
lines changed

2 files changed

+24
-18
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,10 @@ Benchmarks on MacBook Pro M4 Max comparing memory-only Get operations:
4949

5050
| Library | Algorithm | ns/op | Allocations | Persistence |
5151
|---------|-----------|-------|-------------|-------------|
52-
| **bdcache** | S3-FIFO | **12.95** | **0 allocs** | ✅ Auto (Local files + GCP Datastore) |
53-
| golang-lru | LRU | 13.63 | 0 allocs | ❌ None |
54-
| otter | S3-FIFO | 15.73 | 0 allocs | ⚠️ Manual (Save/Load entire cache) |
55-
| ristretto | TinyLFU | 30.43 | 0 allocs | ❌ None |
52+
| **bdcache** | S3-FIFO | **8.61** | **0 allocs** | ✅ Auto (Local files + GCP Datastore) |
53+
| golang-lru | LRU | 13.02 | 0 allocs | ❌ None |
54+
| otter | S3-FIFO | 14.58 | 0 allocs | ⚠️ Manual (Save/Load entire cache) |
55+
| ristretto | TinyLFU | 30.53 | 0 allocs | ❌ None |
5656

5757
> ⚠️ **Benchmark Disclaimer**: These benchmarks are highly cherrypicked to show S3-FIFO's advantages. Different cache implementations excel at different workloads - LRU may outperform S3-FIFO in some scenarios, while TinyLFU shines in others. Performance varies based on access patterns, working set size, and hardware.
5858
>

s3fifo.go

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package bdcache
33
import (
44
"container/list"
55
"sync"
6+
"sync/atomic"
67
"time"
78
)
89

@@ -44,8 +45,8 @@ type entry[K comparable, V any] struct {
4445
key K
4546
value V
4647
element *list.Element
47-
freq int // Access frequency counter
48-
inSmall bool // True if in Small queue, false if in Main
48+
freq atomic.Int32 // Access frequency counter (atomic for lock-free reads)
49+
inSmall bool // True if in Small queue, false if in Main
4950
}
5051

5152
// newS3FIFO creates a new S3-FIFO cache with the given capacity.
@@ -77,26 +78,31 @@ func newS3FIFO[K comparable, V any](capacity int) *s3fifo[K, V] {
7778
// get retrieves a value from the cache.
7879
// On hit, increments frequency counter (used during eviction).
7980
func (c *s3fifo[K, V]) get(key K) (V, bool) {
80-
c.mu.Lock()
81-
defer c.mu.Unlock()
82-
81+
c.mu.RLock()
8382
ent, ok := c.items[key]
8483
if !ok {
84+
c.mu.RUnlock()
8585
var zero V
8686
return zero, false
8787
}
8888

89-
// Check expiration
89+
// Fast path: check expiration while holding read lock
90+
// This is safe because we're only reading ent.expiry
9091
if !ent.expiry.IsZero() && time.Now().After(ent.expiry) {
92+
c.mu.RUnlock()
9193
var zero V
9294
return zero, false
9395
}
9496

97+
val := ent.value
98+
c.mu.RUnlock()
99+
95100
// S3-FIFO: Increment frequency on access (lazy promotion)
96101
// Items are promoted during eviction, not on access
97-
ent.freq++
102+
// Use atomic increment to avoid lock contention on hot path
103+
ent.freq.Add(1)
98104

99-
return ent.value, true
105+
return val, true
100106
}
101107

102108
// set adds or updates a value in the cache.
@@ -108,7 +114,7 @@ func (c *s3fifo[K, V]) set(key K, value V, expiry time.Time) {
108114
if ent, ok := c.items[key]; ok {
109115
ent.value = value
110116
ent.expiry = expiry
111-
ent.freq++
117+
ent.freq.Add(1)
112118
return
113119
}
114120

@@ -126,9 +132,9 @@ func (c *s3fifo[K, V]) set(key K, value V, expiry time.Time) {
126132
key: key,
127133
value: value,
128134
expiry: expiry,
129-
freq: 0,
130135
inSmall: !inGhost,
131136
}
137+
// freq starts at 0 (atomic.Int32 zero value)
132138

133139
// S3-FIFO: Make room if at total capacity
134140
for len(c.items) >= c.capacity {
@@ -193,15 +199,15 @@ func (c *s3fifo[K, V]) evictFromSmall() {
193199
c.small.Remove(elem)
194200

195201
// One-hit wonder: never accessed since insertion
196-
if ent.freq == 0 {
202+
if ent.freq.Load() == 0 {
197203
// Evict and track in ghost queue
198204
delete(c.items, ent.key)
199205
c.addToGhost(ent.key)
200206
return
201207
}
202208

203209
// Hot item: promote to Main queue
204-
ent.freq = 0 // Reset frequency
210+
ent.freq.Store(0) // Reset frequency
205211
ent.inSmall = false
206212
ent.element = c.main.PushBack(ent)
207213
}
@@ -223,7 +229,7 @@ func (c *s3fifo[K, V]) evictFromMain() {
223229
c.main.Remove(elem)
224230

225231
// Not accessed recently: evict
226-
if ent.freq == 0 {
232+
if ent.freq.Load() == 0 {
227233
delete(c.items, ent.key)
228234
// Note: Don't add to ghost - item was already added when evicted from Small
229235
// Only Small queue evictions go to ghost
@@ -232,7 +238,7 @@ func (c *s3fifo[K, V]) evictFromMain() {
232238

233239
// Accessed recently: lazy promotion (FIFO-Reinsertion / Second Chance)
234240
// Move to back of Main queue and decrement frequency
235-
ent.freq--
241+
ent.freq.Add(-1)
236242
ent.element = c.main.PushBack(ent)
237243
}
238244
}

0 commit comments

Comments
 (0)