Compare commits

..

No commits in common. "master" and "v1.0.0" have entirely different histories.

7 changed files with 285 additions and 616 deletions

View File

@ -6,4 +6,3 @@ code was contributed.)
Dustin Sallings <dustin@spy.net>
Jason Mooberry <jasonmoo@me.com>
Sergey Shepelev <temotor@gmail.com>
Alex Edwards <ajmedwards@gmail.com>

View File

@ -1,4 +1,4 @@
Copyright (c) 2012-2019 Patrick Mylund Nielsen and the go-cache contributors
Copyright (c) 2012-2014 Patrick Mylund Nielsen and the go-cache contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

125
README.md
View File

@ -15,69 +15,92 @@ one) to recover from downtime quickly. (See the docs for `NewFrom()` for caveats
### Installation
`go get github.com/patrickmn/go-cache`
`go get github.com/pmylund/go-cache`
### Usage
```go
import (
"fmt"
"github.com/patrickmn/go-cache"
"time"
)
import (
"fmt"
"github.com/pmylund/go-cache"
"time"
)
func main() {
// Create a cache with a default expiration time of 5 minutes, and which
// purges expired items every 10 minutes
c := cache.New(5*time.Minute, 10*time.Minute)
func main() {
// Set the value of the key "foo" to "bar", with the default expiration time
c.Set("foo", "bar", cache.DefaultExpiration)
// Create a cache with a default expiration time of 5 minutes, and which
// purges expired items every 30 seconds
c := cache.New(5*time.Minute, 30*time.Second)
// Set the value of the key "baz" to 42, with no expiration time
// (the item won't be removed until it is re-set, or removed using
// c.Delete("baz")
c.Set("baz", 42, cache.NoExpiration)
// Set the value of the key "foo" to "bar", with the default expiration time
c.Set("foo", "bar", cache.DefaultExpiration)
// Get the string associated with the key "foo" from the cache
foo, found := c.Get("foo")
if found {
fmt.Println(foo)
}
// Set the value of the key "baz" to 42, with no expiration time
// (the item won't be removed until it is re-set, or removed using
// c.Delete("baz")
c.Set("baz", 42, cache.NoExpiration)
// Since Go is statically typed, and cache values can be anything, type
// assertion is needed when values are being passed to functions that don't
// take arbitrary types, (i.e. interface{}). The simplest way to do this for
// values which will only be used once--e.g. for passing to another
// function--is:
foo, found := c.Get("foo")
if found {
MyFunction(foo.(string))
}
// Get the string associated with the key "foo" from the cache
foo, found := c.Get("foo")
if found {
fmt.Println(foo)
}
// This gets tedious if the value is used several times in the same function.
// You might do either of the following instead:
if x, found := c.Get("foo"); found {
foo := x.(string)
// ...
}
// or
var foo string
if x, found := c.Get("foo"); found {
foo = x.(string)
}
// ...
// foo can then be passed around freely as a string
// Since Go is statically typed, and cache values can be anything, type
// assertion is needed when values are being passed to functions that don't
// take arbitrary types, (i.e. interface{}). The simplest way to do this for
// values which will only be used once--e.g. for passing to another
// function--is:
foo, found := c.Get("foo")
if found {
MyFunction(foo.(string))
}
// Want performance? Store pointers!
c.Set("foo", &MyStruct, cache.DefaultExpiration)
if x, found := c.Get("foo"); found {
foo := x.(*MyStruct)
// This gets tedious if the value is used several times in the same function.
// You might do either of the following instead:
if x, found := c.Get("foo"); found {
foo := x.(string)
// ...
}
// or
var foo string
if x, found := c.Get("foo"); found {
foo = x.(string)
}
// ...
// foo can then be passed around freely as a string
// Want performance? Store pointers!
c.Set("foo", &MyStruct, cache.DefaultExpiration)
if x, found := c.Get("foo"); found {
foo := x.(*MyStruct)
// ...
}
// If you store a reference type like a pointer, slice, map or channel, you
// do not need to run Set if you modify the underlying data. The cached
// reference points to the same memory, so if you modify a struct whose
// pointer you've stored in the cache, retrieving that pointer with Get will
// point you to the same data:
foo := &MyStruct{Num: 1}
c.Set("foo", foo, cache.DefaultExpiration)
// ...
x, _ := c.Get("foo")
foo := x.(*MyStruct)
fmt.Println(foo.Num)
// ...
foo.Num++
// ...
x, _ := c.Get("foo")
foo := x.(*MyStruct)
foo.Println(foo.Num)
// will print:
// 1
// 2
}
}
```
### Reference
`godoc` or [http://godoc.org/github.com/patrickmn/go-cache](http://godoc.org/github.com/patrickmn/go-cache)
`godoc` or [http://godoc.org/github.com/pmylund/go-cache](http://godoc.org/github.com/pmylund/go-cache)

495
cache.go

File diff suppressed because it is too large Load Diff

View File

@ -107,14 +107,14 @@ func TestCacheTimes(t *testing.T) {
}
func TestNewFrom(t *testing.T) {
m := map[string]Item{
"a": Item{
m := map[string]*Item{
"a": &Item{
Object: 1,
Expiration: 0,
Expiration: nil,
},
"b": Item{
"b": &Item{
Object: 2,
Expiration: 0,
Expiration: nil,
},
}
tc := NewFrom(DefaultExpiration, 0, m)
@ -1224,29 +1224,6 @@ func TestDecrementUnderflowUint(t *testing.T) {
}
}
func TestOnEvicted(t *testing.T) {
tc := New(DefaultExpiration, 0)
tc.Set("foo", 3, DefaultExpiration)
if tc.onEvicted != nil {
t.Fatal("tc.onEvicted is not nil")
}
works := false
tc.OnEvicted(func(k string, v interface{}) {
if k == "foo" && v.(int) == 3 {
works = true
}
tc.Set("bar", 4, DefaultExpiration)
})
tc.Delete("foo")
x, _ := tc.Get("bar")
if !works {
t.Error("works bool not true")
}
if x.(int) != 4 {
t.Error("bar was not 4")
}
}
func TestCacheSerialization(t *testing.T) {
tc := New(DefaultExpiration, 0)
testFillAndSerialize(t, tc)
@ -1425,17 +1402,9 @@ func TestSerializeUnserializable(t *testing.T) {
}
}
func BenchmarkCacheGetExpiring(b *testing.B) {
benchmarkCacheGet(b, 5*time.Minute)
}
func BenchmarkCacheGetNotExpiring(b *testing.B) {
benchmarkCacheGet(b, NoExpiration)
}
func benchmarkCacheGet(b *testing.B, exp time.Duration) {
func BenchmarkCacheGet(b *testing.B) {
b.StopTimer()
tc := New(exp, 0)
tc := New(DefaultExpiration, 0)
tc.Set("foo", "bar", DefaultExpiration)
b.StartTimer()
for i := 0; i < b.N; i++ {
@ -1457,46 +1426,9 @@ func BenchmarkRWMutexMapGet(b *testing.B) {
}
}
func BenchmarkRWMutexInterfaceMapGetStruct(b *testing.B) {
func BenchmarkCacheGetConcurrent(b *testing.B) {
b.StopTimer()
s := struct{ name string }{name: "foo"}
m := map[interface{}]string{
s: "bar",
}
mu := sync.RWMutex{}
b.StartTimer()
for i := 0; i < b.N; i++ {
mu.RLock()
_, _ = m[s]
mu.RUnlock()
}
}
func BenchmarkRWMutexInterfaceMapGetString(b *testing.B) {
b.StopTimer()
m := map[interface{}]string{
"foo": "bar",
}
mu := sync.RWMutex{}
b.StartTimer()
for i := 0; i < b.N; i++ {
mu.RLock()
_, _ = m["foo"]
mu.RUnlock()
}
}
func BenchmarkCacheGetConcurrentExpiring(b *testing.B) {
benchmarkCacheGetConcurrent(b, 5*time.Minute)
}
func BenchmarkCacheGetConcurrentNotExpiring(b *testing.B) {
benchmarkCacheGetConcurrent(b, NoExpiration)
}
func benchmarkCacheGetConcurrent(b *testing.B, exp time.Duration) {
b.StopTimer()
tc := New(exp, 0)
tc := New(DefaultExpiration, 0)
tc.Set("foo", "bar", DefaultExpiration)
wg := new(sync.WaitGroup)
workers := runtime.NumCPU()
@ -1538,24 +1470,16 @@ func BenchmarkRWMutexMapGetConcurrent(b *testing.B) {
wg.Wait()
}
func BenchmarkCacheGetManyConcurrentExpiring(b *testing.B) {
benchmarkCacheGetManyConcurrent(b, 5*time.Minute)
}
func BenchmarkCacheGetManyConcurrentNotExpiring(b *testing.B) {
benchmarkCacheGetManyConcurrent(b, NoExpiration)
}
func benchmarkCacheGetManyConcurrent(b *testing.B, exp time.Duration) {
func BenchmarkCacheGetManyConcurrent(b *testing.B) {
// This is the same as BenchmarkCacheGetConcurrent, but its result
// can be compared against BenchmarkShardedCacheGetManyConcurrent
// in sharded_test.go.
b.StopTimer()
n := 10000
tc := New(exp, 0)
tc := New(DefaultExpiration, 0)
keys := make([]string, n)
for i := 0; i < n; i++ {
k := "foo" + strconv.Itoa(i)
k := "foo" + strconv.Itoa(n)
keys[i] = k
tc.Set(k, "bar", DefaultExpiration)
}
@ -1563,28 +1487,20 @@ func benchmarkCacheGetManyConcurrent(b *testing.B, exp time.Duration) {
wg := new(sync.WaitGroup)
wg.Add(n)
for _, v := range keys {
go func(k string) {
go func() {
for j := 0; j < each; j++ {
tc.Get(k)
tc.Get(v)
}
wg.Done()
}(v)
}()
}
b.StartTimer()
wg.Wait()
}
func BenchmarkCacheSetExpiring(b *testing.B) {
benchmarkCacheSet(b, 5*time.Minute)
}
func BenchmarkCacheSetNotExpiring(b *testing.B) {
benchmarkCacheSet(b, NoExpiration)
}
func benchmarkCacheSet(b *testing.B, exp time.Duration) {
func BenchmarkCacheSet(b *testing.B) {
b.StopTimer()
tc := New(exp, 0)
tc := New(DefaultExpiration, 0)
b.StartTimer()
for i := 0; i < b.N; i++ {
tc.Set("foo", "bar", DefaultExpiration)
@ -1633,10 +1549,10 @@ func BenchmarkCacheSetDeleteSingleLock(b *testing.B) {
tc := New(DefaultExpiration, 0)
b.StartTimer()
for i := 0; i < b.N; i++ {
tc.mu.Lock()
tc.Lock()
tc.set("foo", "bar", DefaultExpiration)
tc.delete("foo")
tc.mu.Unlock()
tc.Unlock()
}
}
@ -1652,120 +1568,3 @@ func BenchmarkRWMutexMapSetDeleteSingleLock(b *testing.B) {
mu.Unlock()
}
}
func BenchmarkIncrementInt(b *testing.B) {
b.StopTimer()
tc := New(DefaultExpiration, 0)
tc.Set("foo", 0, DefaultExpiration)
b.StartTimer()
for i := 0; i < b.N; i++ {
tc.IncrementInt("foo", 1)
}
}
func BenchmarkDeleteExpiredLoop(b *testing.B) {
b.StopTimer()
tc := New(5*time.Minute, 0)
tc.mu.Lock()
for i := 0; i < 100000; i++ {
tc.set(strconv.Itoa(i), "bar", DefaultExpiration)
}
tc.mu.Unlock()
b.StartTimer()
for i := 0; i < b.N; i++ {
tc.DeleteExpired()
}
}
func TestGetWithExpiration(t *testing.T) {
tc := New(DefaultExpiration, 0)
a, expiration, found := tc.GetWithExpiration("a")
if found || a != nil || !expiration.IsZero() {
t.Error("Getting A found value that shouldn't exist:", a)
}
b, expiration, found := tc.GetWithExpiration("b")
if found || b != nil || !expiration.IsZero() {
t.Error("Getting B found value that shouldn't exist:", b)
}
c, expiration, found := tc.GetWithExpiration("c")
if found || c != nil || !expiration.IsZero() {
t.Error("Getting C found value that shouldn't exist:", c)
}
tc.Set("a", 1, DefaultExpiration)
tc.Set("b", "b", DefaultExpiration)
tc.Set("c", 3.5, DefaultExpiration)
tc.Set("d", 1, NoExpiration)
tc.Set("e", 1, 50*time.Millisecond)
x, expiration, found := tc.GetWithExpiration("a")
if !found {
t.Error("a was not found while getting a2")
}
if x == nil {
t.Error("x for a is nil")
} else if a2 := x.(int); a2+2 != 3 {
t.Error("a2 (which should be 1) plus 2 does not equal 3; value:", a2)
}
if !expiration.IsZero() {
t.Error("expiration for a is not a zeroed time")
}
x, expiration, found = tc.GetWithExpiration("b")
if !found {
t.Error("b was not found while getting b2")
}
if x == nil {
t.Error("x for b is nil")
} else if b2 := x.(string); b2+"B" != "bB" {
t.Error("b2 (which should be b) plus B does not equal bB; value:", b2)
}
if !expiration.IsZero() {
t.Error("expiration for b is not a zeroed time")
}
x, expiration, found = tc.GetWithExpiration("c")
if !found {
t.Error("c was not found while getting c2")
}
if x == nil {
t.Error("x for c is nil")
} else if c2 := x.(float64); c2+1.2 != 4.7 {
t.Error("c2 (which should be 3.5) plus 1.2 does not equal 4.7; value:", c2)
}
if !expiration.IsZero() {
t.Error("expiration for c is not a zeroed time")
}
x, expiration, found = tc.GetWithExpiration("d")
if !found {
t.Error("d was not found while getting d2")
}
if x == nil {
t.Error("x for d is nil")
} else if d2 := x.(int); d2+2 != 3 {
t.Error("d (which should be 1) plus 2 does not equal 3; value:", d2)
}
if !expiration.IsZero() {
t.Error("expiration for d is not a zeroed time")
}
x, expiration, found = tc.GetWithExpiration("e")
if !found {
t.Error("e was not found while getting e2")
}
if x == nil {
t.Error("x for e is nil")
} else if e2 := x.(int); e2+2 != 3 {
t.Error("e (which should be 1) plus 2 does not equal 3; value:", e2)
}
if expiration.UnixNano() != tc.items["e"].Expiration {
t.Error("expiration for e is not the correct time")
}
if expiration.UnixNano() < time.Now().UnixNano() {
t.Error("expiration for e is in the past")
}
}

View File

@ -109,8 +109,8 @@ func (sc *shardedCache) DeleteExpired() {
// fields of the items should be checked. Note that explicit synchronization
// is needed to use a cache and its corresponding Items() return values at
// the same time, as the maps are shared.
func (sc *shardedCache) Items() []map[string]Item {
res := make([]map[string]Item, len(sc.cs))
func (sc *shardedCache) Items() []map[string]*Item {
res := make([]map[string]*Item, len(sc.cs))
for i, v := range sc.cs {
res[i] = v.Items()
}
@ -171,7 +171,7 @@ func newShardedCache(n int, de time.Duration) *shardedCache {
for i := 0; i < n; i++ {
c := &cache{
defaultExpiration: de,
items: map[string]Item{},
items: map[string]*Item{},
}
sc.cs[i] = c
}

View File

@ -4,7 +4,6 @@ import (
"strconv"
"sync"
"testing"
"time"
)
// func TestDjb33(t *testing.T) {
@ -33,17 +32,9 @@ func TestShardedCache(t *testing.T) {
}
}
func BenchmarkShardedCacheGetExpiring(b *testing.B) {
benchmarkShardedCacheGet(b, 5*time.Minute)
}
func BenchmarkShardedCacheGetNotExpiring(b *testing.B) {
benchmarkShardedCacheGet(b, NoExpiration)
}
func benchmarkShardedCacheGet(b *testing.B, exp time.Duration) {
func BenchmarkShardedCacheGet(b *testing.B) {
b.StopTimer()
tc := unexportedNewSharded(exp, 0, 10)
tc := unexportedNewSharded(DefaultExpiration, 0, 10)
tc.Set("foobarba", "zquux", DefaultExpiration)
b.StartTimer()
for i := 0; i < b.N; i++ {
@ -51,21 +42,13 @@ func benchmarkShardedCacheGet(b *testing.B, exp time.Duration) {
}
}
func BenchmarkShardedCacheGetManyConcurrentExpiring(b *testing.B) {
benchmarkShardedCacheGetManyConcurrent(b, 5*time.Minute)
}
func BenchmarkShardedCacheGetManyConcurrentNotExpiring(b *testing.B) {
benchmarkShardedCacheGetManyConcurrent(b, NoExpiration)
}
func benchmarkShardedCacheGetManyConcurrent(b *testing.B, exp time.Duration) {
func BenchmarkShardedCacheGetManyConcurrent(b *testing.B) {
b.StopTimer()
n := 10000
tsc := unexportedNewSharded(exp, 0, 20)
tsc := unexportedNewSharded(DefaultExpiration, 0, 20)
keys := make([]string, n)
for i := 0; i < n; i++ {
k := "foo" + strconv.Itoa(i)
k := "foo" + strconv.Itoa(n)
keys[i] = k
tsc.Set(k, "bar", DefaultExpiration)
}
@ -73,12 +56,12 @@ func benchmarkShardedCacheGetManyConcurrent(b *testing.B, exp time.Duration) {
wg := new(sync.WaitGroup)
wg.Add(n)
for _, v := range keys {
go func(k string) {
go func() {
for j := 0; j < each; j++ {
tsc.Get(k)
tsc.Get(v)
}
wg.Done()
}(v)
}()
}
b.StartTimer()
wg.Wait()