mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 15:09:42 +00:00
(COMMON): Basic (Least Recently Used) LRU caching system (#420)
* started adding basic lru cache system * Added basic LRU cache including Add Get Remove Contains ContainsOrAdd Clear * wording changes on comments * removed exported var's in strut as they are not required * Added README * README updates * rm line :D * swapped to mutex over rwmutex updated comments * unexported getNewest & getOldest * unexported getNewest & getOldest * Updated comments and cited references in source * updated comments
This commit is contained in:
67
common/cache/README.md
vendored
Normal file
67
common/cache/README.md
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
# GoCryptoTrader package cache
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/page-logo.png?raw=true" width="350px" height="350px" hspace="70">
|
||||
|
||||
|
||||
[](https://travis-ci.org/thrasher-corp/gocryptotrader)
|
||||
[](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)
|
||||
[](https://godoc.org/github.com/thrasher-corp/gocryptotrader/portfolio)
|
||||
[](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master)
|
||||
[](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader)
|
||||
|
||||
|
||||
This cache package is part of the GoCryptoTrader codebase.
|
||||
|
||||
## This is still in active development
|
||||
|
||||
You can track ideas, planned features and what's in progress on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader).
|
||||
|
||||
Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTc5ZDE1ZTNiOGM3ZGMyMmY1NTAxYWZhODE0MWM5N2JlZDk1NDU0YTViYzk4NTk3OTRiMDQzNGQ1YTc4YmRlMTk)
|
||||
|
||||
## Current Features for cache package
|
||||
|
||||
+ Basic LRU cache system with both goroutine safe (via mutex locking) and non-goroutine safe options
|
||||
|
||||
## How to use
|
||||
|
||||
##### Basic Usage:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import ("github.com/thrasher-corp/gocryptotrader/common/cache")
|
||||
|
||||
func main() {
|
||||
lruCache := cache.New(5)
|
||||
lruCache.Add("hello", "world")
|
||||
c := lruCache.Contains("hello")
|
||||
if !c {
|
||||
fmt.Println("expected cache to contain \"hello\" key")
|
||||
}
|
||||
|
||||
v := lruCache.Get("hello")
|
||||
if v == nil {
|
||||
fmt.Println("expected cache to contain \"hello\" key")
|
||||
}
|
||||
fmt.Println(v)
|
||||
}
|
||||
```
|
||||
## Contribution
|
||||
|
||||
Please feel free to submit any pull requests or suggest any desired features to be added.
|
||||
|
||||
When submitting a PR, please abide by our coding guidelines:
|
||||
|
||||
+ Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)).
|
||||
+ Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines.
|
||||
+ Code must adhere to our [coding style](https://github.com/thrasher-corp/gocryptotrader/blob/master/doc/coding_style.md).
|
||||
+ Pull requests need to be based on and opened against the `master` branch.
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB***
|
||||
|
||||
75
common/cache/cache.go
vendored
Normal file
75
common/cache/cache.go
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
package cache
|
||||
|
||||
// New returns a new concurrent-safe LRU cache with input capacity
|
||||
func New(capacity uint64) *LRUCache {
|
||||
return &LRUCache{
|
||||
lru: NewLRUCache(capacity),
|
||||
}
|
||||
}
|
||||
|
||||
// Add new entry to Cache return true if entry removed
|
||||
func (l *LRUCache) Add(k, v interface{}) {
|
||||
l.m.Lock()
|
||||
l.lru.Add(k, v)
|
||||
l.m.Unlock()
|
||||
}
|
||||
|
||||
// Get looks up a key's value from the cache.
|
||||
func (l *LRUCache) Get(key interface{}) (value interface{}) {
|
||||
l.m.Lock()
|
||||
defer l.m.Unlock()
|
||||
return l.lru.Get(key)
|
||||
}
|
||||
|
||||
// GetOldest looks up old key's value from the cache.
|
||||
func (l *LRUCache) getOldest() (key, value interface{}) {
|
||||
l.m.Lock()
|
||||
defer l.m.Unlock()
|
||||
return l.lru.getOldest()
|
||||
}
|
||||
|
||||
// getNewest looks up a key's value from the cache.
|
||||
func (l *LRUCache) getNewest() (key, value interface{}) {
|
||||
l.m.Lock()
|
||||
defer l.m.Unlock()
|
||||
return l.lru.getNewest()
|
||||
}
|
||||
|
||||
// ContainsOrAdd checks if cache contains key if not adds to cache
|
||||
func (l *LRUCache) ContainsOrAdd(key, value interface{}) bool {
|
||||
l.m.Lock()
|
||||
defer l.m.Unlock()
|
||||
if l.lru.Contains(key) {
|
||||
return true
|
||||
}
|
||||
l.lru.Add(key, value)
|
||||
return false
|
||||
}
|
||||
|
||||
// Contains checks if cache contains key
|
||||
func (l *LRUCache) Contains(key interface{}) bool {
|
||||
l.m.Lock()
|
||||
defer l.m.Unlock()
|
||||
return l.lru.Contains(key)
|
||||
}
|
||||
|
||||
// Remove entry from cache
|
||||
func (l *LRUCache) Remove(key interface{}) bool {
|
||||
l.m.Lock()
|
||||
defer l.m.Unlock()
|
||||
return l.lru.Remove(key)
|
||||
}
|
||||
|
||||
// Clear is used to clear the cache.
|
||||
func (l *LRUCache) Clear() {
|
||||
l.m.Lock()
|
||||
l.lru.Clear()
|
||||
l.m.Unlock()
|
||||
}
|
||||
|
||||
// Len returns the number of items in the cache.
|
||||
func (l *LRUCache) Len() uint64 {
|
||||
l.m.Lock()
|
||||
defer l.m.Unlock()
|
||||
return l.lru.Len()
|
||||
}
|
||||
145
common/cache/cache_test.go
vendored
Normal file
145
common/cache/cache_test.go
vendored
Normal file
@@ -0,0 +1,145 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCache(t *testing.T) {
|
||||
lruCache := New(5)
|
||||
lruCache.Add("hello", "world")
|
||||
c := lruCache.Contains("hello")
|
||||
if !c {
|
||||
t.Fatal("expected cache to contain \"hello\" key")
|
||||
}
|
||||
|
||||
v := lruCache.Get("hello")
|
||||
if v == nil {
|
||||
t.Fatal("expected cache to contain \"hello\" key")
|
||||
}
|
||||
if v.(string) != "world" {
|
||||
t.Fatal("expected \"hello\" key to contain value \"world\"")
|
||||
}
|
||||
|
||||
r := lruCache.Remove("hello")
|
||||
if !r {
|
||||
t.Fatal("expected \"hello\" key to be removed from cache")
|
||||
}
|
||||
|
||||
v = lruCache.Get("hello")
|
||||
if v != nil {
|
||||
t.Fatal("expected cache to not contain \"hello\" key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainsOrAdd(t *testing.T) {
|
||||
lruCache := New(5)
|
||||
|
||||
if lruCache.ContainsOrAdd("hello", "world") {
|
||||
t.Fatal("expected ContainsOrAdd() to add new key when not found")
|
||||
}
|
||||
|
||||
if !lruCache.ContainsOrAdd("hello", "world") {
|
||||
t.Fatal("expected ContainsOrAdd() to return true when key found")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClear(t *testing.T) {
|
||||
lruCache := New(5)
|
||||
for x := 0; x < 5; x++ {
|
||||
lruCache.Add(x, x)
|
||||
}
|
||||
if lruCache.Len() != 5 {
|
||||
t.Fatal("expected cache to have 5 entries")
|
||||
}
|
||||
lruCache.Clear()
|
||||
if lruCache.Len() != 0 {
|
||||
t.Fatal("expected cache to have 0 entries")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdd(t *testing.T) {
|
||||
lruCache := New(2)
|
||||
lruCache.Add(1, 1)
|
||||
lruCache.Add(2, 2)
|
||||
if lruCache.Len() != 2 {
|
||||
t.Fatal("expected cache to have 2 entries")
|
||||
}
|
||||
lruCache.Add(3, 3)
|
||||
if lruCache.Len() != 2 {
|
||||
t.Fatal("expected cache to have 2 entries")
|
||||
}
|
||||
|
||||
v := lruCache.Get(1)
|
||||
if v != nil {
|
||||
t.Fatal("expected cache to no longer contain \"1\" key")
|
||||
}
|
||||
v = lruCache.Get(2)
|
||||
if v == nil {
|
||||
t.Fatal("expected cache to contain \"2\" key")
|
||||
}
|
||||
if v.(int) != 2 {
|
||||
t.Fatal("expected \"2\" key to contain value \"2\"")
|
||||
}
|
||||
k, v := lruCache.getNewest()
|
||||
if k.(int) != 2 {
|
||||
t.Fatal("expected latest key to be 2")
|
||||
}
|
||||
if v.(int) != 2 {
|
||||
t.Fatal("expected latest value to be 2")
|
||||
}
|
||||
lruCache.Add(3, 3)
|
||||
k, _ = lruCache.getNewest()
|
||||
if k.(int) != 3 {
|
||||
t.Fatal("expected latest key to be 3")
|
||||
}
|
||||
k, _ = lruCache.getOldest()
|
||||
if k.(int) != 2 {
|
||||
t.Fatal("expected oldest key to be 2")
|
||||
}
|
||||
k, v = lruCache.getOldest()
|
||||
if k.(int) != 2 {
|
||||
t.Fatal("expected oldest key to be 2")
|
||||
}
|
||||
if v.(int) != 2 {
|
||||
t.Fatal("expected latest value to be 2")
|
||||
}
|
||||
lruCache.Add(2, 2)
|
||||
k, _ = lruCache.getNewest()
|
||||
if k.(int) != 2 {
|
||||
t.Fatal("expected latest key to be 2")
|
||||
}
|
||||
k, _ = lruCache.getOldest()
|
||||
if k.(int) != 3 {
|
||||
t.Fatal("expected oldest key to be 3")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemove(t *testing.T) {
|
||||
lruCache := New(2)
|
||||
lruCache.Add(1, 1)
|
||||
|
||||
v := lruCache.Remove(1)
|
||||
if !v {
|
||||
t.Fatal("expected remove on valid key to return true")
|
||||
}
|
||||
v = lruCache.Remove(2)
|
||||
if v {
|
||||
t.Fatal("expected remove on invalid key to return false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetNewest(t *testing.T) {
|
||||
lruCache := New(2)
|
||||
k, _ := lruCache.getNewest()
|
||||
if k != nil {
|
||||
t.Fatal("expected GetNewest() on empty cache to return nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetOldest(t *testing.T) {
|
||||
lruCache := New(2)
|
||||
k, _ := lruCache.getOldest()
|
||||
if k != nil {
|
||||
t.Fatal("expected GetOldest() on empty cache to return nil")
|
||||
}
|
||||
}
|
||||
25
common/cache/cache_types.go
vendored
Normal file
25
common/cache/cache_types.go
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// LRUCache thread safe fixed size LRU cache
|
||||
type LRUCache struct {
|
||||
lru *LRU
|
||||
m sync.Mutex
|
||||
}
|
||||
|
||||
// LRU non-thread safe fixed size LRU cache
|
||||
type LRU struct {
|
||||
Cap uint64
|
||||
l *list.List
|
||||
items map[interface{}]*list.Element
|
||||
}
|
||||
|
||||
// item holds key/value for the cache
|
||||
type item struct {
|
||||
key interface{}
|
||||
value interface{}
|
||||
}
|
||||
106
common/cache/lru.go
vendored
Normal file
106
common/cache/lru.go
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
LRU Cache package
|
||||
|
||||
Based off information obtained from:
|
||||
|
||||
https://girai.dev/blog/lru-cache-implementation-in-go/
|
||||
https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_recently_used_(LRU)
|
||||
*/
|
||||
|
||||
package cache
|
||||
|
||||
import "container/list"
|
||||
|
||||
// NewLRUCache returns a new non-concurrent-safe LRU cache with input capacity
|
||||
func NewLRUCache(capacity uint64) *LRU {
|
||||
return &LRU{
|
||||
Cap: capacity,
|
||||
l: list.New(),
|
||||
items: make(map[interface{}]*list.Element),
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds a value to the cache
|
||||
func (l *LRU) Add(key, value interface{}) {
|
||||
if f, o := l.items[key]; o {
|
||||
l.l.MoveToFront(f)
|
||||
f.Value.(*item).value = value
|
||||
return
|
||||
}
|
||||
|
||||
newItem := &item{key, value}
|
||||
itemList := l.l.PushFront(newItem)
|
||||
l.items[key] = itemList
|
||||
if l.Len() > l.Cap {
|
||||
l.removeOldestEntry()
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns keys value from cache if found
|
||||
func (l *LRU) Get(key interface{}) interface{} {
|
||||
if i, f := l.items[key]; f {
|
||||
l.l.MoveToFront(i)
|
||||
return i.Value.(*item).value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetOldest returns the oldest entry
|
||||
func (l *LRU) getOldest() (key, value interface{}) {
|
||||
x := l.l.Back()
|
||||
if x != nil {
|
||||
return x.Value.(*item).key, x.Value.(*item).value
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetNewest returns the newest entry
|
||||
func (l *LRU) getNewest() (key, value interface{}) {
|
||||
x := l.l.Front()
|
||||
if x != nil {
|
||||
return x.Value.(*item).key, x.Value.(*item).value
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Contains check if key is in cache this does not update LRU
|
||||
func (l *LRU) Contains(key interface{}) (f bool) {
|
||||
_, f = l.items[key]
|
||||
return
|
||||
}
|
||||
|
||||
// Remove removes key from the cache, if the key was removed.
|
||||
func (l *LRU) Remove(key interface{}) bool {
|
||||
if i, f := l.items[key]; f {
|
||||
l.removeElement(i)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Clear is used to completely clear the cache.
|
||||
func (l *LRU) Clear() {
|
||||
for x := range l.items {
|
||||
delete(l.items, l.items[x])
|
||||
}
|
||||
l.l.Init()
|
||||
}
|
||||
|
||||
// Len returns length of l
|
||||
func (l *LRU) Len() uint64 {
|
||||
return uint64(l.l.Len())
|
||||
}
|
||||
|
||||
// removeOldest removes the oldest item from the cache.
|
||||
func (l *LRU) removeOldestEntry() {
|
||||
i := l.l.Back()
|
||||
if i != nil {
|
||||
l.removeElement(i)
|
||||
}
|
||||
}
|
||||
|
||||
// removeElement element from the cache
|
||||
func (l *LRU) removeElement(e *list.Element) {
|
||||
l.l.Remove(e)
|
||||
delete(l.items, e.Value.(*item).key)
|
||||
}
|
||||
Reference in New Issue
Block a user