Skip to content

Commit c628fb3

Browse files
Thomas StrombergThomas Stromberg
authored andcommitted
add string fast path
1 parent 6b08cc7 commit c628fb3

File tree

2 files changed

+16
-10
lines changed

2 files changed

+16
-10
lines changed

README.md

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
<br clear="right">
1010

11-
Blazing fast in-memory Go cache with optional L2 persistence layer.
11+
Stupid fast in-memory Go cache with optional L2 persistence layer.
1212

1313
## Install
1414

@@ -65,8 +65,6 @@ cache, _ := bdcache.New[string, User](ctx,
6565
bdcache prioritizes high hit-rates and low read latency, but it performs quite well all around.
6666

6767
Here's the results from an M4 MacBook Pro - run `make bench` to see the results for yourself:
68-
```
69-
════════════════════════════════════════════════════════════════════════════╝
7068

7169
### Hit Rate (Zipf α=0.99, 1M ops, 1M keyspace)
7270

@@ -192,9 +190,8 @@ Here's the results from an M4 MacBook Pro - run `make bench` to see the results
192190

193191
🏆 Get throughput: +0.9% faster than 2nd best (otter)
194192
🏆 Set throughput: +9.1% faster than 2nd best (freecache)
195-
```
196193

197-
There will certainly be benchmarks where other caches perform faster, but no solution blends speed and persistence the way that bdcache does.
194+
NOTE: Performance characteristics often have trade-offs. There are almost certainly workloads where other cache implementations are faster, but nobody blends speed and persistence the way that bdcache does.
198195

199196
## License
200197

s3fifo.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,11 @@ const (
3737
// - If freq == 0 → evict (don't add to ghost, already there)
3838
// - If freq > 0 → reinsert to back of Main and decrement freq (lazy promotion)
3939
type s3fifo[K comparable, V any] struct {
40-
shards [numShards]*shard[K, V]
41-
seed maphash.Seed
42-
keyIsInt bool // Fast path flag for int keys
43-
keyIsInt64 bool // Fast path flag for int64 keys
40+
shards [numShards]*shard[K, V]
41+
seed maphash.Seed
42+
keyIsInt bool // Fast path flag for int keys
43+
keyIsInt64 bool // Fast path flag for int64 keys
44+
keyIsString bool // Fast path flag for string keys
4445
}
4546

4647
// shard is an independent S3-FIFO cache partition.
@@ -95,6 +96,8 @@ func newS3FIFO[K comparable, V any](capacity int) *s3fifo[K, V] {
9596
c.keyIsInt = true
9697
case int64:
9798
c.keyIsInt64 = true
99+
case string:
100+
c.keyIsString = true
98101
}
99102

100103
for i := range numShards {
@@ -133,7 +136,7 @@ func newShard[K comparable, V any](capacity int) *shard[K, V] {
133136

134137
// getShard returns the shard for a given key using type-optimized hashing.
135138
// Uses bitwise AND with shardMask for fast modulo (numShards must be power of 2).
136-
// Fast paths for int and int64 keys avoid the type switch overhead entirely.
139+
// Fast paths for int, int64, and string keys avoid the type switch overhead entirely.
137140
func (c *s3fifo[K, V]) getShard(key K) *shard[K, V] {
138141
// Fast path for int keys (most common case in benchmarks).
139142
// The keyIsInt flag is set once at construction, so this branch is predictable.
@@ -147,6 +150,12 @@ func (c *s3fifo[K, V]) getShard(key K) *shard[K, V] {
147150
k := *(*int64)(unsafe.Pointer(&key))
148151
return c.shards[uint64(k)&shardMask] //nolint:gosec // G115: intentional wrap
149152
}
153+
if c.keyIsString {
154+
// Use unsafe to extract the string without boxing to any.
155+
// This is safe because we only enter this path when K is string.
156+
k := *(*string)(unsafe.Pointer(&key))
157+
return c.shards[maphash.String(c.seed, k)&shardMask]
158+
}
150159
// Slow path: use type switch for other key types
151160
return c.shards[c.shardIndexSlow(key)]
152161
}

0 commit comments

Comments
 (0)