diff --git a/README.md b/README.md index ac6fbaf..c3d9a2f 100644 --- a/README.md +++ b/README.md @@ -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`. diff --git a/fastcache.go b/fastcache.go index 3192cbd..e173642 100644 --- a/fastcache.go +++ b/fastcache.go @@ -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 @@ -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 +} diff --git a/fastcache_test.go b/fastcache_test.go index d90f9f2..b81dcf9 100644 --- a/fastcache_test.go +++ b/fastcache_test.go @@ -1,6 +1,8 @@ package fastcache import ( + "bytes" + "errors" "fmt" "runtime" "sync" @@ -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)) diff --git a/fastcache_timing_test.go b/fastcache_timing_test.go index 9a13df3..c4eaf26 100644 --- a/fastcache_timing_test.go +++ b/fastcache_timing_test.go @@ -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 + }) + } +}