Merge f648931821
into ea4bd2a538
This commit is contained in:
commit
247cddedd3
100
cache.go
100
cache.go
|
@ -1,6 +1,7 @@
|
||||||
package cache
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/petar/GoLLRB/llrb"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
@ -15,6 +16,15 @@ type Item struct {
|
||||||
Expiration int64
|
Expiration int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Node struct {
|
||||||
|
Expiration int64
|
||||||
|
Key string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (node Node) Less(than llrb.Item) bool {
|
||||||
|
return node.Expiration < than.(Node).Expiration
|
||||||
|
}
|
||||||
|
|
||||||
// Returns true if the item has expired.
|
// Returns true if the item has expired.
|
||||||
func (item Item) Expired() bool {
|
func (item Item) Expired() bool {
|
||||||
if item.Expiration == 0 {
|
if item.Expiration == 0 {
|
||||||
|
@ -43,41 +53,53 @@ type cache struct {
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
onEvicted func(string, interface{})
|
onEvicted func(string, interface{})
|
||||||
janitor *janitor
|
janitor *janitor
|
||||||
|
sortedItems *llrb.LLRB
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add an item to the cache, replacing any existing item. If the duration is 0
|
// Add an item to the cache, replacing any existing item. If the duration is 0
|
||||||
// (DefaultExpiration), the cache's default expiration time is used. If it is -1
|
// (DefaultExpiration), the cache's default expiration time is used. If it is -1
|
||||||
// (NoExpiration), the item never expires.
|
// (NoExpiration), the item never expires.
|
||||||
func (c *cache) Set(k string, x interface{}, d time.Duration) {
|
func (c *cache) Set(k string, x interface{}, d time.Duration) {
|
||||||
// "Inlining" of set
|
|
||||||
var e int64
|
|
||||||
if d == DefaultExpiration {
|
|
||||||
d = c.defaultExpiration
|
|
||||||
}
|
|
||||||
if d > 0 {
|
|
||||||
e = time.Now().Add(d).UnixNano()
|
|
||||||
}
|
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
c.items[k] = Item{
|
c.set(k, x, d)
|
||||||
Object: x,
|
|
||||||
Expiration: e,
|
|
||||||
}
|
|
||||||
// TODO: Calls to mu.Unlock are currently not deferred because defer
|
// TODO: Calls to mu.Unlock are currently not deferred because defer
|
||||||
// adds ~200 ns (as of go1.)
|
// adds ~200 ns (as of go1.)
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cache) set(k string, x interface{}, d time.Duration) {
|
func (c *cache) set(k string, x interface{}, d time.Duration) {
|
||||||
var e int64
|
item := Item{Object: x,}
|
||||||
if d == DefaultExpiration {
|
if d == DefaultExpiration {
|
||||||
d = c.defaultExpiration
|
d = c.defaultExpiration
|
||||||
}
|
}
|
||||||
if d > 0 {
|
if d > 0 {
|
||||||
e = time.Now().Add(d).UnixNano()
|
item.Expiration = time.Now().Add(d).UnixNano()
|
||||||
|
old, found := c.items[k]
|
||||||
|
if found && old.Expiration != item.Expiration{
|
||||||
|
c.deleteFromBst(Node{Expiration: old.Expiration, Key: k})
|
||||||
|
c.sortedItems.InsertNoReplace(Node{Expiration: item.Expiration, Key: k})
|
||||||
|
} else if !found {
|
||||||
|
c.sortedItems.InsertNoReplace(Node{Expiration: item.Expiration, Key: k})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
c.items[k] = Item{
|
c.items[k] = item
|
||||||
Object: x,
|
}
|
||||||
Expiration: e,
|
|
||||||
|
func (c *cache) deleteFromBst (node Node) {
|
||||||
|
//delete nodes from the tree with the same expiration
|
||||||
|
//until the required one is found
|
||||||
|
var toReinsert []Node
|
||||||
|
for del := c.sortedItems.Delete(node); del != nil; {
|
||||||
|
delNode := del.(Node)
|
||||||
|
if delNode.Key == node.Key {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
toReinsert = append (toReinsert, delNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//reinsert the nodes in the tree, with modified expiration
|
||||||
|
for _, delNode := range toReinsert {
|
||||||
|
c.sortedItems.InsertNoReplace(delNode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -882,13 +904,13 @@ func (c *cache) Delete(k string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cache) delete(k string) (interface{}, bool) {
|
func (c *cache) delete(k string) (interface{}, bool) {
|
||||||
if c.onEvicted != nil {
|
if v, found := c.items[k]; found {
|
||||||
if v, found := c.items[k]; found {
|
delete(c.items, k)
|
||||||
delete(c.items, k)
|
c.deleteFromBst(Node{Expiration: v.Expiration, Key: k})
|
||||||
|
if c.onEvicted != nil {
|
||||||
return v.Object, true
|
return v.Object, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delete(c.items, k)
|
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -899,21 +921,29 @@ type keyAndValue struct {
|
||||||
|
|
||||||
// Delete all expired items from the cache.
|
// Delete all expired items from the cache.
|
||||||
func (c *cache) DeleteExpired() {
|
func (c *cache) DeleteExpired() {
|
||||||
|
var evictedNodes []Node
|
||||||
var evictedItems []keyAndValue
|
var evictedItems []keyAndValue
|
||||||
now := time.Now().UnixNano()
|
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
for k, v := range c.items {
|
c.sortedItems.DescendLessOrEqual(Node{Expiration: time.Now().UnixNano()}, func(i llrb.Item) bool {
|
||||||
// "Inlining" of expired
|
v := i.(Node)
|
||||||
if v.Expiration > 0 && now > v.Expiration {
|
k := v.Key
|
||||||
ov, evicted := c.delete(k)
|
item, found := c.items[k]
|
||||||
if evicted {
|
if !found {
|
||||||
evictedItems = append(evictedItems, keyAndValue{k, ov})
|
panic("Item in tree but not in map!!")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
delete(c.items, k)
|
||||||
|
evictedItems = append(evictedItems, keyAndValue{k, item.Object})
|
||||||
|
evictedNodes = append(evictedNodes, v)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
for _, v := range evictedNodes {
|
||||||
|
c.sortedItems.Delete(v)
|
||||||
}
|
}
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
for _, v := range evictedItems {
|
if c.onEvicted != nil {
|
||||||
c.onEvicted(v.key, v.value)
|
for _, n := range evictedItems {
|
||||||
|
c.onEvicted(n.key, n.value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -980,6 +1010,9 @@ func (c *cache) Load(r io.Reader) error {
|
||||||
ov, found := c.items[k]
|
ov, found := c.items[k]
|
||||||
if !found || ov.Expired() {
|
if !found || ov.Expired() {
|
||||||
c.items[k] = v
|
c.items[k] = v
|
||||||
|
if !found {
|
||||||
|
c.sortedItems.InsertNoReplace(Node{Expiration: v.Expiration, Key: k})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1035,6 +1068,7 @@ func (c *cache) ItemCount() int {
|
||||||
func (c *cache) Flush() {
|
func (c *cache) Flush() {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
c.items = map[string]Item{}
|
c.items = map[string]Item{}
|
||||||
|
c.sortedItems = llrb.New()
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1077,6 +1111,10 @@ func newCache(de time.Duration, m map[string]Item) *cache {
|
||||||
defaultExpiration: de,
|
defaultExpiration: de,
|
||||||
items: m,
|
items: m,
|
||||||
}
|
}
|
||||||
|
c.sortedItems = llrb.New()
|
||||||
|
for k, item := range m {
|
||||||
|
c.sortedItems.InsertNoReplace(Node{Key: k, Expiration: item.Expiration})
|
||||||
|
}
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1676,3 +1676,46 @@ func BenchmarkDeleteExpiredLoop(b *testing.B) {
|
||||||
tc.DeleteExpired()
|
tc.DeleteExpired()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkLargeCache01(b *testing.B) {
|
||||||
|
benchmarkLargeCache(b, 100000)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkLargeCache02(b *testing.B) {
|
||||||
|
benchmarkLargeCache(b, 200000)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkLargeCache05(b *testing.B) {
|
||||||
|
benchmarkLargeCache(b, 500000)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkLargeCache10(b *testing.B) {
|
||||||
|
benchmarkLargeCache(b, 1000000)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkLargeCache20(b *testing.B) {
|
||||||
|
benchmarkLargeCache(b, 2000000)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkLargeCache50(b *testing.B) {
|
||||||
|
benchmarkLargeCache(b, 5000000)
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchmarkLargeCache(b *testing.B, nano int) {
|
||||||
|
b.StopTimer()
|
||||||
|
tc := New(100*time.Millisecond, time.Duration(nano)*time.Nanosecond)
|
||||||
|
b.StartTimer()
|
||||||
|
b.N = 1000000
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
tc.Set(strconv.Itoa(i), "bar", DefaultExpiration)
|
||||||
|
}
|
||||||
|
b.StopTimer()
|
||||||
|
tc.DeleteExpired()
|
||||||
|
now := time.Now().UnixNano()
|
||||||
|
for _, item := range tc.Items() {
|
||||||
|
if item.Expiration < now {
|
||||||
|
b.Fatalf("some items have not been correctly evicted")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
|
"github.com/petar/GoLLRB/llrb"
|
||||||
)
|
)
|
||||||
|
|
||||||
// This is an experimental and unexported (for now) attempt at making a cache
|
// This is an experimental and unexported (for now) attempt at making a cache
|
||||||
|
@ -172,6 +173,7 @@ func newShardedCache(n int, de time.Duration) *shardedCache {
|
||||||
c := &cache{
|
c := &cache{
|
||||||
defaultExpiration: de,
|
defaultExpiration: de,
|
||||||
items: map[string]Item{},
|
items: map[string]Item{},
|
||||||
|
sortedItems: llrb.New(),
|
||||||
}
|
}
|
||||||
sc.cs[i] = c
|
sc.cs[i] = c
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue