Skip to content

Commit 932eb55

Browse files
Implement cache items visitor.
1 parent 40556fa commit 932eb55

4 files changed

Lines changed: 56 additions & 100 deletions

File tree

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,7 @@ BenchmarkSyncMapGet-4 5000 2316508 ns/op 28.29 MB/s 2543 B/o
4646
BenchmarkSyncMapSetGet-4 2000 10444529 ns/op 12.55 MB/s 3412527 B/op 262210 allocs/op
4747
BenchmarkSaveToFile-4 50 259800249 ns/op 129.15 MB/s 55739129 B/op 3091 allocs/op
4848
BenchmarkLoadFromFile-4 100 121189395 ns/op 276.88 MB/s 98089036 B/op 8748 allocs/op
49-
BenchmarkCache_Keys-8 100000 18359 ns/op 16.34 MB/s 39072 B/op 30 allocs/op
50-
49+
BenchmarkCache_VisitAllEntries-4 50000 245913 ns/op 40.66 MB/s 170 B/op 2 allocs/op
5150
```
5251

5352
`MB/s` column here actually means `millions of operations per second`.

fastcache.go

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package fastcache
22

33
import (
44
"fmt"
5-
"regexp"
65
"sync"
76
"sync/atomic"
87

@@ -122,24 +121,38 @@ func (c *Cache) UpdateStats(s *Stats) {
122121
}
123122
}
124123

125-
// Keys retrieves all cached keys matching regex pattern
126-
func (c *Cache) Keys(pattern string) (keys [][]byte, err error) {
127-
r, err := regexp.Compile(pattern)
128-
if err != nil {
129-
return
130-
}
131-
124+
// VisitAllEntries calls f for all the cache entries.
125+
//
126+
// The function returns immediately if f returns non-nil error.
127+
// It returns the given error.
128+
//
129+
// f cannot hold pointers to k and v contents after returning.
130+
func (c *Cache) VisitAllEntries(f func(k, v []byte) error) error {
132131
for _, b := range c.buckets {
133-
for _, chunk := range b.chunks {
134-
if len(chunk) > 0 {
135-
if key := chunk[4 : 4+chunk[1]]; r.Match(key) {
136-
keys = append(keys, key)
137-
}
132+
b.mu.RLock()
133+
for _, idx := range b.m {
134+
idx &= (1 << bucketSizeBits) - 1
135+
chunkIdx := idx / chunkSize
136+
chunk := b.chunks[chunkIdx]
137+
138+
kvLenBuf := chunk[idx : idx+4]
139+
keyLen := (uint64(kvLenBuf[0]) << 8) | uint64(kvLenBuf[1])
140+
valLen := (uint64(kvLenBuf[2]) << 8) | uint64(kvLenBuf[3])
141+
142+
idx += 4
143+
key := chunk[idx : idx+keyLen]
144+
145+
idx += keyLen
146+
value := chunk[idx : idx+valLen]
147+
148+
if err := f(key, value); err != nil {
149+
return err
138150
}
139151
}
152+
b.mu.RUnlock()
140153
}
141154

142-
return
155+
return nil
143156
}
144157

145158
type bucket struct {

fastcache_test.go

Lines changed: 14 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -166,73 +166,26 @@ func TestCacheGetSetConcurrent(t *testing.T) {
166166
}
167167
}
168168

169-
func TestCacheKeys(t *testing.T) {
170-
keys := []string{
171-
"username",
172-
"firstname",
173-
"lastname",
174-
}
175-
176-
c := New(100 * len(keys))
169+
func TestCacheVisitAllEntries(t *testing.T) {
170+
itemsCount := 10000
171+
c := New(30 * itemsCount)
177172
defer c.Reset()
178173

179-
for _, k := range keys {
180-
c.Set([]byte(k), nil)
181-
}
174+
data := make(map[string][]byte)
182175

183-
tests := []struct {
184-
Pattern string
185-
Expected []string
186-
}{
187-
{
188-
Pattern: "",
189-
Expected: keys,
190-
},
191-
{
192-
Pattern: "name$",
193-
Expected: keys,
194-
},
195-
{
196-
Pattern: "st",
197-
Expected: []string{
198-
"firstname",
199-
"lastname",
200-
},
201-
},
202-
{
203-
Pattern: " ",
204-
Expected: nil,
205-
},
176+
for i := 0; i < itemsCount; i++ {
177+
k := []byte(fmt.Sprintf("key %d", i))
178+
v := []byte(fmt.Sprintf("value %d", i))
179+
c.Set(k, v)
180+
data[string(k)] = v
206181
}
207182

208-
for _, tt := range tests {
209-
result, err := c.Keys(tt.Pattern)
210-
if err != nil {
211-
t.Fatal(err)
183+
_ = c.VisitAllEntries(func(k, v []byte) error {
184+
if string(data[string(k)]) != string(v) {
185+
t.Fatal("error fetching (k, v) pair")
212186
}
213-
214-
count := 0
215-
for _, r := range result {
216-
for _, e := range tt.Expected {
217-
if string(r) == e {
218-
count++
219-
break
220-
}
221-
}
222-
}
223-
224-
if count != len(tt.Expected) {
225-
t.Fatalf("failed to retrievs keys by pattern: \"%s\"", tt.Pattern)
226-
}
227-
}
228-
229-
result, err := c.Keys("*")
230-
if result != nil {
231-
t.Fatal("expected no matches")
232-
}
233-
if err == nil {
234-
t.Fatal("expected regex error")
235-
}
187+
return nil
188+
})
236189
}
237190

238191
func testCacheGetSet(c *Cache, itemsCount int) error {

fastcache_timing_test.go

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -388,35 +388,26 @@ func BenchmarkSyncMapSetGet(b *testing.B) {
388388
})
389389
}
390390

391-
func BenchmarkCache_Keys(b *testing.B) {
392-
keys := []string{
393-
"username",
394-
"firstname",
395-
"lastname",
396-
}
397-
pattern := "name$"
398-
399-
items := 100 * len(keys)
400-
401-
c := New(items)
391+
func BenchmarkCache_VisitAllEntries(b *testing.B) {
392+
itemsCount := 10000
393+
c := New(30 * itemsCount)
402394
defer c.Reset()
403395

404396
b.ReportAllocs()
405-
b.SetBytes(int64(items))
397+
b.SetBytes(int64(itemsCount))
406398

407-
for _, k := range keys {
408-
c.Set([]byte(k), nil)
399+
data := make(map[string][]byte)
400+
401+
for i := 0; i < itemsCount; i++ {
402+
k := []byte(fmt.Sprintf("key %d", i))
403+
v := []byte(fmt.Sprintf("value %d", i))
404+
c.Set(k, v)
405+
data[string(k)] = v
409406
}
410407

411408
for n := 0; n < b.N; n++ {
412-
keys, err := c.Keys(pattern)
413-
414-
if err != nil {
415-
panic(fmt.Errorf("BUG: error for valid pattern \"%s\" (%s)", pattern, err))
416-
}
417-
418-
if len(keys) == 0 {
419-
panic(fmt.Errorf("BUG: no key for valid pattern \"%s\"", pattern))
420-
}
409+
_ = c.VisitAllEntries(func(k, v []byte) error {
410+
return nil
411+
})
421412
}
422413
}

0 commit comments

Comments
 (0)