Skip to content

Commit

Permalink
Merge pull request #26 from hyp3rd/development
Browse files Browse the repository at this point in the history
tuning
  • Loading branch information
hyp3rd authored Jan 26, 2023
2 parents 5648487 + ea2006b commit d888412
Show file tree
Hide file tree
Showing 5 changed files with 30 additions and 19 deletions.
2 changes: 1 addition & 1 deletion backend/redis.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func (cacheBackend *Redis) Capacity() int {
return cacheBackend.capacity
}

// Size returns the number of items in the cache.
// Count returns the number of items in the cache.
func (cacheBackend *Redis) Count() int {
count, _ := cacheBackend.rdb.DBSize(context.Background()).Result()
return int(count)
Expand Down
2 changes: 1 addition & 1 deletion datastructure/v3/cmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type ConcurrentMap struct {
hasher hash.Hash32
}

// ConcurrentMapShared is a "thread" safe string to `*models.Item`.
// ConcurrentMapShard is a "thread" safe string to `*models.Item`.
type ConcurrentMapShard struct {
items map[string]*models.Item
sync.RWMutex
Expand Down
19 changes: 14 additions & 5 deletions hypercache.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type HyperCache[T backend.IBackendConstrain] struct {
evictionAlgorithm eviction.IAlgorithm // `evictionAlgorithm` eviction algorithm to use when evicting items
expirationInterval time.Duration // `expirationInterval` interval at which the expiration loop should run
evictionInterval time.Duration // interval at which the eviction loop should run
shouldEvict atomic.Bool // `shouldEvict` indicates whether the cache should evict items or not
maxEvictionCount uint // `evictionInterval` maximum number of items that can be evicted in a single eviction loop iteration
maxCacheSize int64 // maxCacheSize instructs the cache not allocate more memory than this limit, value in MB, 0 means no limit
memoryAllocation atomic.Int64 // memoryAllocation is the current memory allocation of the cache, value in bytes
Expand Down Expand Up @@ -122,6 +123,9 @@ func New[T backend.IBackendConstrain](config *Config[T]) (hyperCache *HyperCache
// Apply options
ApplyHyperCacheOptions(hyperCache, config.HyperCacheOptions...)

// evaluate if the cache should evict items proactively
hyperCache.shouldEvict.Store(hyperCache.evictionInterval == 0 && hyperCache.backend.Capacity() > 0)

// Set the max eviction count to the capacity if it is not set or is zero
if hyperCache.maxEvictionCount == 0 {
hyperCache.maxEvictionCount = uint(hyperCache.backend.Capacity())
Expand Down Expand Up @@ -153,7 +157,7 @@ func New[T backend.IBackendConstrain](config *Config[T]) (hyperCache *HyperCache

// If the capacity is less than zero, we return
if hyperCache.backend.Capacity() < 0 {
return
return nil, errors.ErrInvalidCapacity
}

// Initialize the expiration trigger channel with the buffer size set to half the capacity
Expand Down Expand Up @@ -264,7 +268,6 @@ func (hyperCache *HyperCache[T]) evictionLoop() {
}

key, ok := hyperCache.evictionAlgorithm.Evict()

if !ok {
// no more items to evict
break
Expand Down Expand Up @@ -316,6 +319,7 @@ func (hyperCache *HyperCache[T]) Set(key string, value any, expiration time.Dura
// check if adding this item will exceed the maxCacheSize
hyperCache.memoryAllocation.Add(item.Size)
if hyperCache.maxCacheSize > 0 && hyperCache.memoryAllocation.Load() > hyperCache.maxCacheSize {
hyperCache.memoryAllocation.Add(-item.Size)
return errors.ErrCacheFull
}

Expand All @@ -330,14 +334,14 @@ func (hyperCache *HyperCache[T]) Set(key string, value any, expiration time.Dura
hyperCache.evictionAlgorithm.Set(key, item.Value)

// If the cache is at capacity, evict an item when the eviction interval is zero
if hyperCache.evictionInterval == 0 && hyperCache.backend.Capacity() > 0 && hyperCache.backend.Count() > hyperCache.backend.Capacity() {
if hyperCache.shouldEvict.Load() && hyperCache.backend.Count() > hyperCache.backend.Capacity() {
hyperCache.evictItem()
}

return nil
}

// Get retrieves the item with the given key from the cache.
// Get retrieves the item with the given key from the cache returning the value and a boolean indicating if the item was found.
func (hyperCache *HyperCache[T]) Get(key string) (value any, ok bool) {
item, ok := hyperCache.backend.Get(key)
if !ok {
Expand All @@ -358,6 +362,7 @@ func (hyperCache *HyperCache[T]) Get(key string) (value any, ok bool) {
return item.Value, true
}

// GetWithInfo retrieves the item with the given key from the cache returning the `Item` object and a boolean indicating if the item was found.
func (hyperCache *HyperCache[T]) GetWithInfo(key string) (*models.Item, bool) {
item, ok := hyperCache.backend.Get(key)
// Check if the item has expired if it exists, if so, trigger the expiration loop
Expand Down Expand Up @@ -419,6 +424,8 @@ func (hyperCache *HyperCache[T]) GetOrSet(key string, value any, expiration time
// check if adding this item will exceed the maxCacheSize
hyperCache.memoryAllocation.Add(item.Size)
if hyperCache.maxCacheSize > 0 && hyperCache.memoryAllocation.Load() > hyperCache.maxCacheSize {
hyperCache.memoryAllocation.Add(-item.Size)
models.ItemPool.Put(item)
return nil, errors.ErrCacheFull
}

Expand All @@ -433,7 +440,7 @@ func (hyperCache *HyperCache[T]) GetOrSet(key string, value any, expiration time
// Set the item in the eviction algorithm
hyperCache.evictionAlgorithm.Set(key, item.Value)
// If the cache is at capacity, evict an item when the eviction interval is zero
if hyperCache.evictionInterval == 0 && hyperCache.backend.Capacity() > 0 && hyperCache.backend.Count() > hyperCache.backend.Capacity() {
if hyperCache.shouldEvict.Load() && hyperCache.backend.Count() > hyperCache.backend.Capacity() {
models.ItemPool.Put(item)
hyperCache.evictItem()
}
Expand Down Expand Up @@ -579,6 +586,8 @@ func (hyperCache *HyperCache[T]) Capacity() int {
func (hyperCache *HyperCache[T]) SetCapacity(capacity int) {
// set capacity of the backend
hyperCache.backend.SetCapacity(capacity)
// evaluate again if the cache should evict items proactively
hyperCache.shouldEvict.Swap(hyperCache.evictionInterval == 0 && hyperCache.backend.Capacity() > 0)
// if the cache size is greater than the new capacity, evict items
if hyperCache.backend.Count() > hyperCache.Capacity() {
hyperCache.evictionLoop()
Expand Down
2 changes: 1 addition & 1 deletion middleware/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ func (mw StatsCollectorMiddleware) Allocation() int64 {
return mw.next.Allocation()
}

// Countze returns the count of the items in the cache
// Count returns the count of the items in the cache
func (mw StatsCollectorMiddleware) Count() int {
return mw.next.Count()
}
Expand Down
24 changes: 13 additions & 11 deletions models/item.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ var (
// },
// }

// buf is a buffer used to calculate the size of the item.
buf []byte

// encoderPool is a pool of encoders used to calculate the size of the item.
Expand All @@ -50,43 +51,44 @@ type Item struct {
AccessCount uint // AccessCount of times the item has been accessed
}

// Size returns the size of the Item in bytes
// func (i *Item) SetSize() error {
// SetSize stores the size of the Item in bytes
// func (item *Item) SetSize() error {
// // Get an encoder from the pool
// enc := encoderPool.Get().(*gob.Encoder)
// // Reset the buffer and put the encoder back in the pool
// defer buf.Reset()
// defer encoderPool.Put(enc)

// // Encode the item
// if err := enc.Encode(i.Value); err != nil {
// if err := enc.Encode(item.Value); err != nil {
// return errors.ErrInvalidSize
// }
// // Set the size of the item
// i.Size = int64(buf.Len())
// item.Size = int64(buf.Len())
// return nil
// }

func (i *Item) SetSize() error {
// SetSize stores the size of the Item in bytes
func (item *Item) SetSize() error {
enc := encoderPool.Get().(*codec.Encoder)
defer encoderPool.Put(enc)
if err := enc.Encode(i.Value); err != nil {
if err := enc.Encode(item.Value); err != nil {
return errors.ErrInvalidSize
}

i.Size = int64(len(buf))
item.Size = int64(len(buf))
buf = buf[:0]
return nil
}

// SizeMB returns the size of the Item in megabytes
func (i *Item) SizeMB() float64 {
return float64(i.Size) / (1024 * 1024)
func (item *Item) SizeMB() float64 {
return float64(item.Size) / (1024 * 1024)
}

// SizeKB returns the size of the Item in kilobytes
func (i *Item) SizeKB() float64 {
return float64(i.Size) / 1024
func (item *Item) SizeKB() float64 {
return float64(item.Size) / 1024
}

// Touch updates the last access time of the item and increments the access count.
Expand Down

0 comments on commit d888412

Please sign in to comment.