289b30715d
This commit converts the dependency management from Godeps to the vendor folder, also switching the tool from godep to trash. Since the upstream tool lacks a few features proposed via a few PRs, until those PRs are merged in (if), use github.com/karalabe/trash. You can update dependencies via trash --update. All dependencies have been updated to their latest version. Parts of the build system are reworked to drop old notions of Godeps and invocation of the go vet command so that it doesn't run against the vendor folder, as that will just blow up during vetting. The conversion drops OpenCL (and hence GPU mining support) from ethash and our codebase. The short reasoning is that there's noone to maintain and having opencl libs in our deps messes up builds as go install ./... tries to build them, failing with unsatisfied link errors for the C OpenCL deps. golang.org/x/net/context is not vendored in. We expect it to be fetched by the user (i.e. using go get). To keep ci.go builds reproducible the package is "vendored" in build/_vendor.
258 lines
5.5 KiB
Go
258 lines
5.5 KiB
Go
package lru
|
|
|
|
import (
|
|
"sync"
|
|
|
|
"github.com/hashicorp/golang-lru/simplelru"
|
|
)
|
|
|
|
// ARCCache is a thread-safe fixed size Adaptive Replacement Cache (ARC).
|
|
// ARC is an enhancement over the standard LRU cache in that tracks both
|
|
// frequency and recency of use. This avoids a burst in access to new
|
|
// entries from evicting the frequently used older entries. It adds some
|
|
// additional tracking overhead to a standard LRU cache, computationally
|
|
// it is roughly 2x the cost, and the extra memory overhead is linear
|
|
// with the size of the cache. ARC has been patented by IBM, but is
|
|
// similar to the TwoQueueCache (2Q) which requires setting parameters.
|
|
type ARCCache struct {
|
|
size int // Size is the total capacity of the cache
|
|
p int // P is the dynamic preference towards T1 or T2
|
|
|
|
t1 *simplelru.LRU // T1 is the LRU for recently accessed items
|
|
b1 *simplelru.LRU // B1 is the LRU for evictions from t1
|
|
|
|
t2 *simplelru.LRU // T2 is the LRU for frequently accessed items
|
|
b2 *simplelru.LRU // B2 is the LRU for evictions from t2
|
|
|
|
lock sync.RWMutex
|
|
}
|
|
|
|
// NewARC creates an ARC of the given size
|
|
func NewARC(size int) (*ARCCache, error) {
|
|
// Create the sub LRUs
|
|
b1, err := simplelru.NewLRU(size, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
b2, err := simplelru.NewLRU(size, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
t1, err := simplelru.NewLRU(size, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
t2, err := simplelru.NewLRU(size, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Initialize the ARC
|
|
c := &ARCCache{
|
|
size: size,
|
|
p: 0,
|
|
t1: t1,
|
|
b1: b1,
|
|
t2: t2,
|
|
b2: b2,
|
|
}
|
|
return c, nil
|
|
}
|
|
|
|
// Get looks up a key's value from the cache.
|
|
func (c *ARCCache) Get(key interface{}) (interface{}, bool) {
|
|
c.lock.Lock()
|
|
defer c.lock.Unlock()
|
|
|
|
// Ff the value is contained in T1 (recent), then
|
|
// promote it to T2 (frequent)
|
|
if val, ok := c.t1.Peek(key); ok {
|
|
c.t1.Remove(key)
|
|
c.t2.Add(key, val)
|
|
return val, ok
|
|
}
|
|
|
|
// Check if the value is contained in T2 (frequent)
|
|
if val, ok := c.t2.Get(key); ok {
|
|
return val, ok
|
|
}
|
|
|
|
// No hit
|
|
return nil, false
|
|
}
|
|
|
|
// Add adds a value to the cache.
|
|
func (c *ARCCache) Add(key, value interface{}) {
|
|
c.lock.Lock()
|
|
defer c.lock.Unlock()
|
|
|
|
// Check if the value is contained in T1 (recent), and potentially
|
|
// promote it to frequent T2
|
|
if c.t1.Contains(key) {
|
|
c.t1.Remove(key)
|
|
c.t2.Add(key, value)
|
|
return
|
|
}
|
|
|
|
// Check if the value is already in T2 (frequent) and update it
|
|
if c.t2.Contains(key) {
|
|
c.t2.Add(key, value)
|
|
return
|
|
}
|
|
|
|
// Check if this value was recently evicted as part of the
|
|
// recently used list
|
|
if c.b1.Contains(key) {
|
|
// T1 set is too small, increase P appropriately
|
|
delta := 1
|
|
b1Len := c.b1.Len()
|
|
b2Len := c.b2.Len()
|
|
if b2Len > b1Len {
|
|
delta = b2Len / b1Len
|
|
}
|
|
if c.p+delta >= c.size {
|
|
c.p = c.size
|
|
} else {
|
|
c.p += delta
|
|
}
|
|
|
|
// Potentially need to make room in the cache
|
|
if c.t1.Len()+c.t2.Len() >= c.size {
|
|
c.replace(false)
|
|
}
|
|
|
|
// Remove from B1
|
|
c.b1.Remove(key)
|
|
|
|
// Add the key to the frequently used list
|
|
c.t2.Add(key, value)
|
|
return
|
|
}
|
|
|
|
// Check if this value was recently evicted as part of the
|
|
// frequently used list
|
|
if c.b2.Contains(key) {
|
|
// T2 set is too small, decrease P appropriately
|
|
delta := 1
|
|
b1Len := c.b1.Len()
|
|
b2Len := c.b2.Len()
|
|
if b1Len > b2Len {
|
|
delta = b1Len / b2Len
|
|
}
|
|
if delta >= c.p {
|
|
c.p = 0
|
|
} else {
|
|
c.p -= delta
|
|
}
|
|
|
|
// Potentially need to make room in the cache
|
|
if c.t1.Len()+c.t2.Len() >= c.size {
|
|
c.replace(true)
|
|
}
|
|
|
|
// Remove from B2
|
|
c.b2.Remove(key)
|
|
|
|
// Add the key to the frequntly used list
|
|
c.t2.Add(key, value)
|
|
return
|
|
}
|
|
|
|
// Potentially need to make room in the cache
|
|
if c.t1.Len()+c.t2.Len() >= c.size {
|
|
c.replace(false)
|
|
}
|
|
|
|
// Keep the size of the ghost buffers trim
|
|
if c.b1.Len() > c.size-c.p {
|
|
c.b1.RemoveOldest()
|
|
}
|
|
if c.b2.Len() > c.p {
|
|
c.b2.RemoveOldest()
|
|
}
|
|
|
|
// Add to the recently seen list
|
|
c.t1.Add(key, value)
|
|
return
|
|
}
|
|
|
|
// replace is used to adaptively evict from either T1 or T2
|
|
// based on the current learned value of P
|
|
func (c *ARCCache) replace(b2ContainsKey bool) {
|
|
t1Len := c.t1.Len()
|
|
if t1Len > 0 && (t1Len > c.p || (t1Len == c.p && b2ContainsKey)) {
|
|
k, _, ok := c.t1.RemoveOldest()
|
|
if ok {
|
|
c.b1.Add(k, nil)
|
|
}
|
|
} else {
|
|
k, _, ok := c.t2.RemoveOldest()
|
|
if ok {
|
|
c.b2.Add(k, nil)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Len returns the number of cached entries
|
|
func (c *ARCCache) Len() int {
|
|
c.lock.RLock()
|
|
defer c.lock.RUnlock()
|
|
return c.t1.Len() + c.t2.Len()
|
|
}
|
|
|
|
// Keys returns all the cached keys
|
|
func (c *ARCCache) Keys() []interface{} {
|
|
c.lock.RLock()
|
|
defer c.lock.RUnlock()
|
|
k1 := c.t1.Keys()
|
|
k2 := c.t2.Keys()
|
|
return append(k1, k2...)
|
|
}
|
|
|
|
// Remove is used to purge a key from the cache
|
|
func (c *ARCCache) Remove(key interface{}) {
|
|
c.lock.Lock()
|
|
defer c.lock.Unlock()
|
|
if c.t1.Remove(key) {
|
|
return
|
|
}
|
|
if c.t2.Remove(key) {
|
|
return
|
|
}
|
|
if c.b1.Remove(key) {
|
|
return
|
|
}
|
|
if c.b2.Remove(key) {
|
|
return
|
|
}
|
|
}
|
|
|
|
// Purge is used to clear the cache
|
|
func (c *ARCCache) Purge() {
|
|
c.lock.Lock()
|
|
defer c.lock.Unlock()
|
|
c.t1.Purge()
|
|
c.t2.Purge()
|
|
c.b1.Purge()
|
|
c.b2.Purge()
|
|
}
|
|
|
|
// Contains is used to check if the cache contains a key
|
|
// without updating recency or frequency.
|
|
func (c *ARCCache) Contains(key interface{}) bool {
|
|
c.lock.RLock()
|
|
defer c.lock.RUnlock()
|
|
return c.t1.Contains(key) || c.t2.Contains(key)
|
|
}
|
|
|
|
// Peek is used to inspect the cache value of a key
|
|
// without updating recency or frequency.
|
|
func (c *ARCCache) Peek(key interface{}) (interface{}, bool) {
|
|
c.lock.RLock()
|
|
defer c.lock.RUnlock()
|
|
if val, ok := c.t1.Peek(key); ok {
|
|
return val, ok
|
|
}
|
|
return c.t2.Peek(key)
|
|
}
|