From a35f42bc2e4c29a1583fbc8900c14ec0ce5533d2 Mon Sep 17 00:00:00 2001 From: Marc Pervaz Boocha Date: Mon, 24 Feb 2025 10:37:50 +0530 Subject: Added examples and documentation --- conn.go | 62 +++++++++++++---- conn_test.go | 145 +++++++++++++++++++++++++++++++++++++++ encoding_test.go | 2 +- evict.go | 27 +++++++- examples/basic_usage/main.go | 42 ++++++++++++ examples/eviction_policy/main.go | 39 +++++++++++ store.go | 27 ++++++-- store_test.go | 68 ++++++++++++++---- 8 files changed, 378 insertions(+), 34 deletions(-) create mode 100644 conn_test.go create mode 100644 examples/basic_usage/main.go create mode 100644 examples/eviction_policy/main.go diff --git a/conn.go b/conn.go index 065bf7a..911c2bc 100644 --- a/conn.go +++ b/conn.go @@ -11,6 +11,7 @@ import ( "github.com/vmihailenco/msgpack/v5" ) + // db represents a cache database with file-backed storage and in-memory operation. type db struct { File io.WriteSeeker Store store @@ -18,8 +19,10 @@ type db struct { wg sync.WaitGroup } + // Option is a function type for configuring the db. type Option func(*db) error + // openFile opens a file-backed cache database with the given options. func openFile(filename string, options ...Option) (*db, error) { ret, err := openMem(options...) if err != nil { @@ -47,6 +50,7 @@ func openFile(filename string, options ...Option) (*db, error) { return ret, nil } + // openMem initializes an in-memory cache database with the given options. func openMem(options ...Option) (*db, error) { ret := &db{} ret.Store.Init() @@ -54,12 +58,14 @@ func openMem(options ...Option) (*db, error) { return ret, nil } + // Start begins the background worker for periodic tasks. func (d *db) Start() { d.Stop = make(chan struct{}) d.wg.Add(1) go d.backgroundWorker() } + // SetConfig applies configuration options to the db. func (d *db) SetConfig(options ...Option) error { d.Store.mu.Lock() defer d.Store.mu.Unlock() @@ -72,12 +78,14 @@ func (d *db) SetConfig(options ...Option) error { return nil } + // WithPolicy sets the eviction policy for the cache. func WithPolicy(e EvictionPolicyType) Option { return func(d *db) error { return d.Store.Policy.SetPolicy(e) } } + // WithMaxCost sets the maximum cost for the cache. func WithMaxCost(maxCost uint64) Option { return func(d *db) error { d.Store.MaxCost = maxCost @@ -85,6 +93,7 @@ func WithMaxCost(maxCost uint64) Option { } } + // SetSnapshotTime sets the interval for taking snapshots of the cache. func SetSnapshotTime(t time.Duration) Option { return func(d *db) error { d.Store.SnapshotTicker.Reset(t) @@ -92,6 +101,7 @@ func SetSnapshotTime(t time.Duration) Option { } } + // SetCleanupTime sets the interval for cleaning up expired entries. func SetCleanupTime(t time.Duration) Option { return func(d *db) error { d.Store.CleanupTicker.Reset(t) @@ -99,6 +109,7 @@ func SetCleanupTime(t time.Duration) Option { } } + // backgroundWorker performs periodic tasks such as snapshotting and cleanup. func (d *db) backgroundWorker() { defer d.wg.Done() @@ -121,6 +132,7 @@ func (d *db) backgroundWorker() { } } + // Close stops the background worker and cleans up resources. func (d *db) Close() { close(d.Stop) d.wg.Wait() @@ -134,6 +146,7 @@ func (d *db) Close() { } } + // Flush writes the current state of the store to the file. func (d *db) Flush() error { if d.File != nil { return d.Store.Snapshot(d.File) @@ -141,17 +154,20 @@ func (d *db) Flush() error { return nil } + // Clear removes all entries from the in-memory store. func (d *db) Clear() { d.Store.Clear() } -var ErrKeyNotFound = errors.New("key not found") +var ErrKeyNotFound = errors.New("key not found") // ErrKeyNotFound is returned when a key is not found in the cache. // The Cache database. Can be initialized by either OpenFile or OpenMem. Uses per DB Locks. + // DB represents a generic cache database with key-value pairs. type DB[K any, V any] struct { *db } + // OpenFile opens a file-backed cache database with the specified options. func OpenFile[K any, V any](filename string, options ...Option) (DB[K, V], error) { ret, err := openFile(filename, options...) if err != nil { @@ -161,7 +177,8 @@ func OpenFile[K any, V any](filename string, options ...Option) (DB[K, V], error return DB[K, V]{db: ret}, nil } -func OpenMem[K any, V any](filename string, options ...Option) (DB[K, V], error) { + // OpenMem initializes an in-memory cache database with the specified options. +func OpenMem[K any, V any](options ...Option) (DB[K, V], error) { ret, err := openMem(options...) if err != nil { return zero[DB[K, V]](), err @@ -170,33 +187,49 @@ func OpenMem[K any, V any](filename string, options ...Option) (DB[K, V], error) return DB[K, V]{db: ret}, nil } -func (h *DB[K, V]) Get(key K, value V) (V, time.Duration, error) { - keyData, err := msgpack.Marshal(key) + // marshal serializes a value using msgpack. +func marshal[T any](v T) ([]byte, error) { + return msgpack.Marshal(v) +} + + // unmarshal deserializes data into a value using msgpack. +func unmarshal[T any](data []byte, v *T) error { + return msgpack.Unmarshal(data, v) +} + + // Get retrieves a value from the cache by key and returns its TTL. +func (h *DB[K, V]) Get(key K, value *V) (time.Duration, error) { + keyData, err := marshal(key) if err != nil { - return value, 0, err + return 0, err } v, ttl, ok := h.Store.Get(keyData) if !ok { - return value, 0, ErrKeyNotFound + return 0, ErrKeyNotFound } - - if err = msgpack.Unmarshal(v, value); err != nil { - return value, 0, err + if v != nil { + if err = unmarshal(v, value); err != nil { + return 0, err + } } - return value, ttl, err + return ttl, err } + // GetValue retrieves a value from the cache by key and returns the value and its TTL. func (h *DB[K, V]) GetValue(key K) (V, time.Duration, error) { - return h.Get(key, zero[V]()) + value := zero[V]() + ttl, err := h.Get(key, &value) + return value, ttl, err } + // Set adds a key-value pair to the cache with a specified TTL. func (h *DB[K, V]) Set(key K, value V, ttl time.Duration) error { - keyData, err := msgpack.Marshal(key) + keyData, err := marshal(key) if err != nil { return err } - valueData, err := msgpack.Marshal(value) + valueData, err := marshal(value) if err != nil { return err } @@ -204,8 +237,9 @@ func (h *DB[K, V]) Set(key K, value V, ttl time.Duration) error { return nil } + // Delete removes a key-value pair from the cache. func (h *DB[K, V]) Delete(key K) error { - keyData, err := msgpack.Marshal(key) + keyData, err := marshal(key) if err != nil { return err } diff --git a/conn_test.go b/conn_test.go new file mode 100644 index 0000000..d1581f3 --- /dev/null +++ b/conn_test.go @@ -0,0 +1,145 @@ +package cache + +import ( + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func setupTestDB[K any, V any](t testing.TB) *DB[K, V] { + t.Helper() + + db, err := OpenMem[K, V]() + assert.NoError(t, err) + t.Cleanup(func() { + db.Close() + }) + return &db +} + +func TestDBGetSet(t *testing.T) { + t.Parallel() + + t.Run("Exists", func(t *testing.T) { + t.Parallel() + + db := setupTestDB[string, string](t) + + want := "Value" + err := db.Set("Key", want, 1*time.Hour) + assert.NoError(t, err) + + got, ttl, err := db.GetValue("Key") + assert.NoError(t, err) + assert.Equal(t, want, got) + + now := time.Now() + assert.WithinDuration(t, now.Add(ttl), now.Add(1*time.Hour), 1*time.Millisecond) + }) + + t.Run("Not Exists", func(t *testing.T) { + t.Parallel() + + db := setupTestDB[string, string](t) + + _, _, err := db.GetValue("Key") + assert.ErrorIs(t, err, ErrKeyNotFound) + }) + + t.Run("Update", func(t *testing.T) { + t.Parallel() + + db := setupTestDB[string, string](t) + + err := db.Set("Key", "Other", 0) + assert.NoError(t, err) + + want := "Value" + err = db.Set("Key", want, 0) + assert.NoError(t, err) + + got, _, err := db.GetValue("Key") + assert.NoError(t, err) + assert.Equal(t, want, got) + }) +} + +func TestDBDelete(t *testing.T) { + t.Parallel() + + t.Run("Exists", func(t *testing.T) { + t.Parallel() + + db := setupTestDB[string, string](t) + want := "Value" + err := db.Set("Key", want, 0) + assert.NoError(t, err) + + err = db.Delete("Key") + assert.NoError(t, err) + + _, _, err = db.GetValue("Key") + assert.ErrorIs(t, err, ErrKeyNotFound) + }) + + t.Run("Not Exists", func(t *testing.T) { + t.Parallel() + + db := setupTestDB[string, string](t) + + err := db.Delete("Key") + assert.ErrorIs(t, err, ErrKeyNotFound) + }) +} + +func BenchmarkDBGet(b *testing.B) { + for n := 1; n <= 10000; n *= 10 { + b.Run(fmt.Sprint(n), func(b *testing.B) { + db := setupTestDB[int, int](b) + for i := 0; i < n; i++ { + db.Set(i, i, 0) + } + b.ReportAllocs() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + db.GetValue(n - 1) + } + }) + } +} + +func BenchmarkDBSet(b *testing.B) { + for n := 1; n <= 10000; n *= 10 { + b.Run(fmt.Sprint(n), func(b *testing.B) { + db := setupTestDB[int, int](b) + for i := 0; i < n-1; i++ { + db.Set(i, i, 0) + } + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + db.Set(n, n, 0) + } + }) + } +} + +func BenchmarkDBDelete(b *testing.B) { + for n := 1; n <= 10000; n *= 10 { + b.Run(fmt.Sprint(n), func(b *testing.B) { + db := setupTestDB[int, int](b) + for i := 0; i < n-1; i++ { + db.Set(i, i, 0) + } + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + db.Set(n, n, 0) + db.Delete(n) + } + }) + } +} diff --git a/encoding_test.go b/encoding_test.go index 6ca50cb..5fc4c9a 100644 --- a/encoding_test.go +++ b/encoding_test.go @@ -288,7 +288,7 @@ func BenchmarkDecoder_DecodeStore(b *testing.B) { for n := 1; n <= 10000; n *= 10 { b.Run(fmt.Sprint(n), func(b *testing.B) { want := setupTestStore(b) - for i := range 100 { + for i := range n { buf := make([]byte, 8) binary.LittleEndian.PutUint64(buf, uint64(i)) want.Set(buf, buf, 0) diff --git a/evict.go b/evict.go index 73f2cec..ec4e848 100644 --- a/evict.go +++ b/evict.go @@ -7,7 +7,8 @@ import ( // EvictionPolicyType defines the type of eviction policy. type EvictionPolicyType int -const ( +const ( + // PolicyNone indicates no eviction policy. PolicyNone EvictionPolicyType = iota PolicyFIFO PolicyLRU @@ -16,6 +17,7 @@ const ( ) // evictionStrategies interface defines the methods for eviction strategies. + // evictionStrategies interface defines the methods for eviction strategies. type evictionStrategies interface { OnInsert(n *node) OnUpdate(n *node) @@ -24,12 +26,14 @@ type evictionStrategies interface { } // evictionPolicy struct holds the eviction strategy and its type. + // evictionPolicy struct holds the eviction strategy and its type. type evictionPolicy struct { evictionStrategies Type EvictionPolicyType evict *node } + // pushEvict adds a node to the eviction list. func pushEvict(node *node, sentinnel *node) { node.EvictPrev = sentinnel node.EvictNext = node.EvictPrev.EvictNext @@ -38,6 +42,7 @@ func pushEvict(node *node, sentinnel *node) { } // SetPolicy sets the eviction policy based on the given type. + // SetPolicy sets the eviction policy based on the given type. func (e *evictionPolicy) SetPolicy(y EvictionPolicyType) error { store := map[EvictionPolicyType]func() evictionStrategies{ PolicyNone: func() evictionStrategies { @@ -65,25 +70,30 @@ func (e *evictionPolicy) SetPolicy(y EvictionPolicyType) error { } // fifoPolicy struct represents the First-In-First-Out eviction policy. + // fifoPolicy struct represents the First-In-First-Out eviction policy. type fifoPolicy struct { evict *node shouldEvict bool } // OnInsert adds a node to the eviction list. + // OnInsert adds a node to the eviction list. func (s fifoPolicy) OnInsert(node *node) { pushEvict(node, s.evict) } // OnAccess is a no-op for fifoPolicy. + // OnAccess is a no-op for fifoPolicy. func (fifoPolicy) OnAccess(n *node) { } // OnUpdate is a no-op for fifoPolicy. + // OnUpdate is a no-op for fifoPolicy. func (fifoPolicy) OnUpdate(n *node) { } // Evict returns the oldest node for fifoPolicy. + // Evict returns the oldest node for fifoPolicy. func (s fifoPolicy) Evict() *node { if s.shouldEvict && s.evict.EvictPrev != s.evict { return s.evict.EvictPrev @@ -93,20 +103,24 @@ func (s fifoPolicy) Evict() *node { } // lruPolicy struct represents the Least Recently Used eviction policy. + // lruPolicy struct represents the Least Recently Used eviction policy. type lruPolicy struct { evict *node } // OnInsert adds a node to the eviction list. + // OnInsert adds a node to the eviction list. func (s lruPolicy) OnInsert(node *node) { pushEvict(node, s.evict) } + // OnUpdate moves the accessed node to the front of the eviction list. func (s lruPolicy) OnUpdate(node *node) { s.OnAccess(node) } // OnAccess moves the accessed node to the front of the eviction list. + // OnAccess moves the accessed node to the front of the eviction list. func (s lruPolicy) OnAccess(node *node) { node.EvictNext.EvictPrev = node.EvictPrev node.EvictPrev.EvictNext = node.EvictNext @@ -114,6 +128,7 @@ func (s lruPolicy) OnAccess(node *node) { } // Evict returns the least recently used node for lruPolicy. + // Evict returns the least recently used node for lruPolicy. func (s lruPolicy) Evict() *node { if s.evict.EvictPrev != s.evict { return s.evict.EvictPrev @@ -123,21 +138,25 @@ func (s lruPolicy) Evict() *node { } // lfuPolicy struct represents the Least Frequently Used eviction policy. + // lfuPolicy struct represents the Least Frequently Used eviction policy. type lfuPolicy struct { evict *node } // OnInsert adds a node to the eviction list and initializes its access count. + // OnInsert adds a node to the eviction list and initializes its access count. func (s lfuPolicy) OnInsert(node *node) { pushEvict(node, s.evict) } // OnUpdate increments the access count of the node and reorders the list. + // OnUpdate increments the access count of the node and reorders the list. func (s lfuPolicy) OnUpdate(node *node) { s.OnAccess(node) } // OnAccess increments the access count of the node and reorders the list. + // OnAccess increments the access count of the node and reorders the list. func (s lfuPolicy) OnAccess(node *node) { node.Access++ @@ -163,6 +182,7 @@ func (s lfuPolicy) OnAccess(node *node) { } // Evict returns the least frequently used node for LFU. + // Evict returns the least frequently used node for LFU. func (s lfuPolicy) Evict() *node { if s.evict.EvictPrev != s.evict { return s.evict.EvictPrev @@ -172,6 +192,7 @@ func (s lfuPolicy) Evict() *node { } // ltrPolicy struct represents the Least Remaining Time eviction policy. + // ltrPolicy struct represents the Least Remaining Time eviction policy. type ltrPolicy struct { evict *node evictZero bool @@ -179,6 +200,7 @@ type ltrPolicy struct { // OnInsert adds a node to the eviction list based on its TTL (Time To Live). // It places the node in the correct position in the list based on TTL. + // OnInsert adds a node to the eviction list based on its TTL (Time To Live). func (s ltrPolicy) OnInsert(node *node) { pushEvict(node, s.evict) @@ -187,11 +209,13 @@ func (s ltrPolicy) OnInsert(node *node) { // OnAccess is a no-op for ltrPolicy. // It does not perform any action when a node is accessed. + // OnAccess is a no-op for ltrPolicy. func (s ltrPolicy) OnAccess(node *node) { } // OnUpdate updates the position of the node in the eviction list based on its TTL. // It reorders the list to maintain the correct order based on TTL. + // OnUpdate updates the position of the node in the eviction list based on its TTL. func (s ltrPolicy) OnUpdate(node *node) { if node.TTL() == 0 { return @@ -230,6 +254,7 @@ func (s ltrPolicy) OnUpdate(node *node) { // Evict returns the node with the least remaining time to live for ltrPolicy. // It returns the node at the end of the eviction list. + // Evict returns the node with the least remaining time to live for ltrPolicy. func (s ltrPolicy) Evict() *node { if s.evict.EvictPrev != s.evict && (s.evict.EvictPrev.TTL() != 0 || s.evictZero) { return s.evict.EvictPrev diff --git a/examples/basic_usage/main.go b/examples/basic_usage/main.go new file mode 100644 index 0000000..0582770 --- /dev/null +++ b/examples/basic_usage/main.go @@ -0,0 +1,42 @@ +package main + +import ( + "fmt" + "time" + + "github.com/marcthe12/cache" +) + +func main() { + // Create an in-memory cache + db, err := cache.OpenMem[string, string]("example") + if err != nil { + fmt.Println("Error:", err) + return + } + defer db.Close() + + // Set a value with a TTL of 5 seconds + err = db.Set("key1", "value1", 5*time.Second) + if err != nil { + fmt.Println("Set Error:", err) + return + } + + // Get the value + value, ttl, err := db.GetValue("key1") + if err != nil { + fmt.Println("Get Error:", err) + } else { + fmt.Printf("Got value: %s, TTL: %s\n", value, ttl) + } + + // Wait for 6 seconds and try to get the value again + time.Sleep(6 * time.Second) + value, ttl, err = db.GetValue("key1") + if err != nil { + fmt.Println("Get Error after TTL:", err) + } else { + fmt.Printf("Got value after TTL: %s, TTL: %s\n", value, ttl) + } +} diff --git a/examples/eviction_policy/main.go b/examples/eviction_policy/main.go new file mode 100644 index 0000000..a9c8577 --- /dev/null +++ b/examples/eviction_policy/main.go @@ -0,0 +1,39 @@ +package main + +import ( + "fmt" + + "code.qburst.com/marcpervaz//cache" +) + +func main() { + // Create an in-memory cache with LRU eviction policy + db, err := cache.OpenMem[string, string]("example", cache.WithPolicy(cache.PolicyLRU)) + if err != nil { + fmt.Println("Error:", err) + return + } + defer db.Close() + + // Set values + db.Set("key1", "value1", 0) + db.Set("key2", "value2", 0) + db.Set("key3", "value3", 0) + + // Access some keys + db.GetValue("key1") + db.GetValue("key2") + + // Add another key to trigger eviction + db.Set("key4", "value4", 0) + + // Check which keys are present + for _, key := range []string{"key1", "key2", "key3", "key4"} { + value, _, err := db.GetValue(key) + if err != nil { + fmt.Printf("Key %s not found\n", key) + } else { + fmt.Printf("Key %s found with value: %s\n", key, value) + } + } +} diff --git a/store.go b/store.go index f4ba952..8747cd2 100644 --- a/store.go +++ b/store.go @@ -10,6 +10,7 @@ import ( const initialBucketSize uint64 = 8 + // node represents an entry in the cache with metadata for eviction and expiration. type node struct { Hash uint64 Expiration time.Time @@ -22,10 +23,12 @@ type node struct { EvictPrev *node } + // IsValid checks if the node is still valid based on its expiration time. func (n *node) IsValid() bool { return n.Expiration.IsZero() || n.Expiration.After(time.Now()) } + // TTL returns the time-to-live of the node. func (n *node) TTL() time.Duration { if n.Expiration.IsZero() { return 0 @@ -34,6 +37,7 @@ func (n *node) TTL() time.Duration { } } + // store represents the in-memory cache with eviction policies and periodic tasks. type store struct { Bucket []node Length uint64 @@ -46,6 +50,7 @@ type store struct { mu sync.Mutex } + // Init initializes the store with default settings. func (s *store) Init() { s.Clear() s.Policy.evict = &s.Evict @@ -54,6 +59,7 @@ func (s *store) Init() { s.Policy.SetPolicy(PolicyNone) } + // Clear removes all entries from the store. func (s *store) Clear() { s.mu.Lock() defer s.mu.Unlock() @@ -66,11 +72,13 @@ func (s *store) Clear() { s.Evict.EvictPrev = &s.Evict } + // lookup calculates the hash and index for a given key. func lookup(s *store, key []byte) (uint64, uint64) { hash := hash(key) return hash % uint64(len(s.Bucket)), hash } + // lazyInitBucket initializes the hash bucket if it hasn't been initialized yet. func lazyInitBucket(n *node) { if n.HashNext == nil { n.HashNext = n @@ -78,6 +86,7 @@ func lazyInitBucket(n *node) { } } + // lookup finds a node in the store by key. func (s *store) lookup(key []byte) (*node, uint64, uint64) { idx, hash := lookup(s, key) @@ -94,6 +103,7 @@ func (s *store) lookup(key []byte) (*node, uint64, uint64) { return nil, idx, hash } + // get retrieves a value from the store by key. func (s *store) get(key []byte) ([]byte, time.Duration, bool) { v, _, _ := s.lookup(key) if v != nil { @@ -108,6 +118,7 @@ func (s *store) get(key []byte) ([]byte, time.Duration, bool) { return nil, 0, false } + // Get retrieves a value from the store by key with locking. func (s *store) Get(key []byte) ([]byte, time.Duration, bool) { s.mu.Lock() defer s.mu.Unlock() @@ -115,14 +126,15 @@ func (s *store) Get(key []byte) ([]byte, time.Duration, bool) { return s.get(key) } + // resize doubles the size of the hash table and rehashes all entries. func resize(s *store) { bucket := make([]node, 2*len(s.Bucket)) for v := s.Evict.EvictNext; v != &s.Evict; v = v.EvictNext { - if !v.IsValid() { - deleteNode(s, v) - continue - } + // if !v.IsValid() { + // deleteNode(s, v) + // continue + // } idx := v.Hash % uint64(len(bucket)) n := &bucket[idx] @@ -137,6 +149,7 @@ func resize(s *store) { s.Bucket = bucket } + // cleanup removes expired entries from the store. func cleanup(s *store) { for v := s.Evict.EvictNext; v != &s.Evict; v = v.EvictNext { if !v.IsValid() { @@ -145,6 +158,7 @@ func cleanup(s *store) { } } + // evict removes entries from the store based on the eviction policy. func evict(s *store) bool { for s.MaxCost != 0 && s.MaxCost < s.Cost { n := s.Policy.Evict() @@ -156,6 +170,7 @@ func evict(s *store) bool { return true } + // set adds or updates a key-value pair in the store. func (s *store) set(key []byte, value []byte, ttl time.Duration) { v, idx, hash := s.lookup(key) if v != nil { @@ -195,6 +210,7 @@ func (s *store) set(key []byte, value []byte, ttl time.Duration) { s.Length = s.Length + 1 } + // Set adds or updates a key-value pair in the store with locking. func (s *store) Set(key []byte, value []byte, ttl time.Duration) { s.mu.Lock() defer s.mu.Unlock() @@ -202,6 +218,7 @@ func (s *store) Set(key []byte, value []byte, ttl time.Duration) { s.set(key, value, ttl) } + // deleteNode removes a node from the store. func deleteNode(s *store, v *node) { v.HashNext.HashPrev = v.HashPrev v.HashPrev.HashNext = v.HashNext @@ -217,6 +234,7 @@ func deleteNode(s *store, v *node) { s.Length = s.Length - 1 } + // delete removes a key-value pair from the store. func (s *store) delete(key []byte) bool { v, _, _ := s.lookup(key) if v != nil { @@ -227,6 +245,7 @@ func (s *store) delete(key []byte) bool { return false } + // Delete removes a key-value pair from the store with locking. func (s *store) Delete(key []byte) bool { s.mu.Lock() defer s.mu.Unlock() diff --git a/store_test.go b/store_test.go index 3ad33e5..9b802bf 100644 --- a/store_test.go +++ b/store_test.go @@ -2,6 +2,7 @@ package cache import ( "encoding/binary" + "fmt" "testing" "time" @@ -132,24 +133,63 @@ func TestStoreClear(t *testing.T) { } func BenchmarkStoreGet(b *testing.B) { - store := setupTestStore(b) - - key := []byte("Key") - store.Set(key, []byte("Store"), 0) - b.ReportAllocs() - - for i := 0; i < b.N; i++ { - store.Get(key) + for n := 1; n <= 10000; n *= 10 { + b.Run(fmt.Sprint(n), func(b *testing.B) { + want := setupTestStore(b) + for i := range n - 1 { + buf := make([]byte, 8) + binary.LittleEndian.PutUint64(buf, uint64(i)) + want.Set(buf, buf, 0) + } + key := []byte("Key") + want.Set(key, []byte("Store"), 0) + b.ReportAllocs() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + want.Get(key) + } + }) } } func BenchmarkStoreSet(b *testing.B) { - store := setupTestStore(b) - - key := []byte("Key") - b.ReportAllocs() + for n := 1; n <= 10000; n *= 10 { + b.Run(fmt.Sprint(n), func(b *testing.B) { + want := setupTestStore(b) + for i := range n - 1 { + buf := make([]byte, 8) + binary.LittleEndian.PutUint64(buf, uint64(i)) + want.Set(buf, buf, 0) + } + key := []byte("Key") + store := []byte("Store") + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + want.Set(key, store, 0) + } + }) + } +} - for i := 0; i < b.N; i++ { - store.Set(key, []byte("Store"), 0) +func BenchmarkStoreDelete(b *testing.B) { + for n := 1; n <= 10000; n *= 10 { + b.Run(fmt.Sprint(n), func(b *testing.B) { + want := setupTestStore(b) + for i := range n - 1 { + buf := make([]byte, 8) + binary.LittleEndian.PutUint64(buf, uint64(i)) + want.Set(buf, buf, 0) + } + key := []byte("Key") + store := []byte("Store") + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + want.Set(key, store, 0) + want.Delete(key) + } + }) } } -- cgit v1.2.3-70-g09d2