Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 16 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,22 @@ GOMAXPROCS=4 go test github.com/VictoriaMetrics/fastcache -bench='Set|Get' -benc
goos: linux
goarch: amd64
pkg: github.com/VictoriaMetrics/fastcache
BenchmarkBigCacheSet-4 2000 10566656 ns/op 6.20 MB/s 4660369 B/op 6 allocs/op
BenchmarkBigCacheGet-4 2000 6902694 ns/op 9.49 MB/s 684169 B/op 131076 allocs/op
BenchmarkBigCacheSetGet-4 1000 17579118 ns/op 7.46 MB/s 5046744 B/op 131083 allocs/op
BenchmarkCacheSet-4 5000 3808874 ns/op 17.21 MB/s 1142 B/op 2 allocs/op
BenchmarkCacheGet-4 5000 3293849 ns/op 19.90 MB/s 1140 B/op 2 allocs/op
BenchmarkCacheSetGet-4 2000 8456061 ns/op 15.50 MB/s 2857 B/op 5 allocs/op
BenchmarkStdMapSet-4 2000 10559382 ns/op 6.21 MB/s 268413 B/op 65537 allocs/op
BenchmarkStdMapGet-4 5000 2687404 ns/op 24.39 MB/s 2558 B/op 13 allocs/op
BenchmarkStdMapSetGet-4 100 154641257 ns/op 0.85 MB/s 387405 B/op 65558 allocs/op
BenchmarkSyncMapSet-4 500 24703219 ns/op 2.65 MB/s 3426543 B/op 262411 allocs/op
BenchmarkSyncMapGet-4 5000 2265892 ns/op 28.92 MB/s 2545 B/op 79 allocs/op
BenchmarkSyncMapSetGet-4 1000 14595535 ns/op 8.98 MB/s 3417190 B/op 262277 allocs/op

BenchmarkBigCacheSet-4 2000 10937855 ns/op 5.99 MB/s 4660369 B/op 6 allocs/op
BenchmarkBigCacheGet-4 2000 6985426 ns/op 9.38 MB/s 684169 B/op 131076 allocs/op
BenchmarkBigCacheSetGet-4 1000 17301294 ns/op 7.58 MB/s 5046746 B/op 131083 allocs/op
BenchmarkCacheSet-4 5000 3975946 ns/op 16.48 MB/s 1142 B/op 2 allocs/op
BenchmarkCacheGet-4 5000 3572679 ns/op 18.34 MB/s 1141 B/op 2 allocs/op
BenchmarkCacheSetGet-4 2000 9337256 ns/op 14.04 MB/s 2856 B/op 5 allocs/op
BenchmarkStdMapSet-4 2000 14684273 ns/op 4.46 MB/s 268423 B/op 65537 allocs/op
BenchmarkStdMapGet-4 5000 2833647 ns/op 23.13 MB/s 2561 B/op 13 allocs/op
BenchmarkStdMapSetGet-4 100 137417861 ns/op 0.95 MB/s 387356 B/op 65558 allocs/op
BenchmarkSyncMapSet-4 1000 23300189 ns/op 2.81 MB/s 3417183 B/op 262277 allocs/op
BenchmarkSyncMapGet-4 5000 2316508 ns/op 28.29 MB/s 2543 B/op 79 allocs/op
BenchmarkSyncMapSetGet-4 2000 10444529 ns/op 12.55 MB/s 3412527 B/op 262210 allocs/op
BenchmarkSaveToFile-4 50 259800249 ns/op 129.15 MB/s 55739129 B/op 3091 allocs/op
BenchmarkLoadFromFile-4 100 121189395 ns/op 276.88 MB/s 98089036 B/op 8748 allocs/op
BenchmarkCache_VisitAllEntries-4 50000 245913 ns/op 40.66 MB/s 170 B/op 2 allocs/op
```

`MB/s` column here actually means `millions of operations per second`.
Expand Down
49 changes: 49 additions & 0 deletions fastcache.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,24 @@ func (c *Cache) UpdateStats(s *Stats) {
s.InvalidValueHashErrors += atomic.LoadUint64(&c.bigStats.InvalidValueHashErrors)
}

// VisitAllEntries calls f for all the cache entries.
//
// The function returns immediately if f returns non-nil error.
// It returns the error returned by f.
//
// f is called sequentially for all the entries in the cache.
// f cannot hold pointers to k and v contents after returning.
// f cannot modify k and v contents.
func (c *Cache) VisitAllEntries(f func(k, v []byte) error) error {
for _, b := range c.buckets {
if err := b.VisitAllEntries(f); err != nil {
return err
}
}

return nil
}

type bucket struct {
mu sync.RWMutex

Expand Down Expand Up @@ -382,3 +400,34 @@ func (b *bucket) Del(h uint64) {
delete(b.m, h)
b.mu.Unlock()
}

func (b *bucket) VisitAllEntries(f func(k, v []byte) error) error {
b.mu.RLock()
for _, v := range b.m {
idx := v & ((1 << bucketSizeBits) - 1)
gen := v >> bucketSizeBits

if gen == b.gen && idx < b.idx || gen+1 == b.gen && idx >= b.idx {
chunkIdx := idx / chunkSize
chunk := b.chunks[chunkIdx]

kvLenBuf := chunk[idx : idx+4]
keyLen := (uint64(kvLenBuf[0]) << 8) | uint64(kvLenBuf[1])
valLen := (uint64(kvLenBuf[2]) << 8) | uint64(kvLenBuf[3])

idx += 4
key := chunk[idx : idx+keyLen : idx+keyLen]

idx += keyLen
value := chunk[idx : idx+valLen : idx+valLen]

if err := f(key, value); err != nil {
b.mu.RUnlock()
return err
}
}
}
b.mu.RUnlock()

return nil
}
47 changes: 47 additions & 0 deletions fastcache_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package fastcache

import (
"bytes"
"errors"
"fmt"
"runtime"
"sync"
Expand Down Expand Up @@ -168,6 +170,51 @@ func TestCacheGetSetConcurrent(t *testing.T) {
}
}

func TestCacheVisitAllEntries(t *testing.T) {
itemsCount := 10000
c := New(30 * itemsCount)
defer c.Reset()

expected := make(map[string][]byte)

for i := 0; i < itemsCount; i++ {
k := []byte(fmt.Sprintf("key %d", i))
v := []byte(fmt.Sprintf("value %d", i))
c.Set(k, v)
expected[string(k)] = v
}

result := make(map[string][]byte)

err := c.VisitAllEntries(func(k, v []byte) error {
result[string(k)] = v
return nil
})

if err != nil {
t.Fatalf("error was not expected: %s", err)
}

if len(result) != len(expected) {
t.Fatalf("visitor returned %d items instead of %d", len(result), len(expected))
}

for k, v := range expected {
if bytes.Compare(result[k], v) != 0 {
t.Fatalf("invalid value %v for key \"%s\", expected %v", result[k], k, v)
}
}

callBackErr := errors.New("err")
err = c.VisitAllEntries(func(k, v []byte) error {
return callBackErr
})

if err != callBackErr {
t.Fatal("error was expected")
}
}

func testCacheGetSet(c *Cache, itemsCount int) error {
for i := 0; i < itemsCount; i++ {
k := []byte(fmt.Sprintf("key %d", i))
Expand Down
24 changes: 24 additions & 0 deletions fastcache_timing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -387,3 +387,27 @@ func BenchmarkSyncMapSetGet(b *testing.B) {
}
})
}

func BenchmarkCache_VisitAllEntries(b *testing.B) {
itemsCount := 10000
c := New(30 * itemsCount)
defer c.Reset()

b.ReportAllocs()
b.SetBytes(int64(itemsCount))

data := make(map[string][]byte)

for i := 0; i < itemsCount; i++ {
k := []byte(fmt.Sprintf("key %d", i))
v := []byte(fmt.Sprintf("value %d", i))
c.Set(k, v)
data[string(k)] = v
}

for n := 0; n < b.N; n++ {
_ = c.VisitAllEntries(func(k, v []byte) error {
return nil
})
}
}