diff --git a/.gitignore b/.gitignore index a1338d6..7fdb081 100644 --- a/.gitignore +++ b/.gitignore @@ -7,8 +7,13 @@ # Test binary, build with `go test -c` *.test +*.lock # Output of the go coverage tool, specifically when used with LiteIDE *.out # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 .glide/ + +.lock + +vendor diff --git a/Gopkg.lock b/Gopkg.lock deleted file mode 100644 index b6fc84b..0000000 --- a/Gopkg.lock +++ /dev/null @@ -1,36 +0,0 @@ -# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. - - -[[projects]] - name = "github.com/allegro/bigcache" - packages = [ - ".", - "queue" - ] - revision = "f31987a23e44c5121ef8c8b2f2ea2e8ffa37b068" - version = "v1.1.0" - -[[projects]] - branch = "master" - name = "github.com/bluele/gcache" - packages = ["."] - revision = "472614239ac7e5bc6461e237c798a6ebd5aff8c1" - -[[projects]] - name = "github.com/kpango/glg" - packages = ["."] - revision = "cb0c8857f1215695015b871c1a877874f81878b0" - version = "v1.2.1" - -[[projects]] - name = "github.com/patrickmn/go-cache" - packages = ["."] - revision = "a3647f8e31d79543b2d0f0ae2fe5c379d72cedc0" - version = "v2.1.0" - -[solve-meta] - analyzer-name = "dep" - analyzer-version = 1 - inputs-digest = "3de15909f5528ba26b373a5914d2ba26ff4d98b2160dd33467d77dd5839e0b8e" - solver-name = "gps-cdcl" - solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml deleted file mode 100644 index 46493fd..0000000 --- a/Gopkg.toml +++ /dev/null @@ -1,46 +0,0 @@ -# Gopkg.toml example -# -# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html -# for detailed Gopkg.toml documentation. -# -# required = ["github.com/user/thing/cmd/thing"] -# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] -# -# [[constraint]] -# name = "github.com/user/project" -# version = "1.0.0" -# -# [[constraint]] -# name = "github.com/user/project2" -# branch = "dev" -# source = "github.com/myfork/project2" -# -# [[override]] -# name = "github.com/x/y" -# version = "2.4.0" -# -# [prune] -# non-go = false -# go-tests = true -# unused-packages = true - - -[[constraint]] - name = "github.com/allegro/bigcache" - version = "1.1.0" - -[[constraint]] - branch = "master" - name = "github.com/bluele/gcache" - -[[constraint]] - name = "github.com/kpango/glg" - version = "1.2.1" - -[[constraint]] - name = "github.com/patrickmn/go-cache" - version = "2.1.0" - -[prune] - go-tests = true - unused-packages = true diff --git a/Makefile b/Makefile index f180958..0f7c4ca 100644 --- a/Makefile +++ b/Makefile @@ -44,5 +44,5 @@ contributors: git log --format='%aN <%aE>' | sort -fu > CONTRIBUTORS update: - glide upgrade - glide update + rm -rf Gopkg.* vendor + dep init diff --git a/README.md b/README.md index 362709c..948411c 100644 --- a/README.md +++ b/README.md @@ -38,9 +38,117 @@ go get github.com/kpango/gache ``` ## Benchmarks -[gache](https://github.com/kpango/gache) vs [normal map with lock](https://github.com/kpango/gache/blob/master/gache_bench_test.go#L13-L35) vs [bigcache](https://github.com/allegro/bigcache) vs [go-cache](https://github.com/patrickmn/go-cache) vs [gcache](https://github.com/bluele/gcache) +[gache](https://github.com/kpango/gache) vs [gocache](https://github.com/hlts2/gocache) vs [normal map with lock](https://github.com/kpango/gache/blob/master/gache_bench_test.go#L13-L35) vs [go-cache](https://github.com/patrickmn/go-cache) vs [gcache](https://github.com/bluele/gcache) vs [freecache](https://github.com/coocood/freecache) vs [bigcache](https://github.com/allegro/bigcache) vs [go-mcache](https://github.com/OrlovEvgeny/go-mcache) -![Bench](https://github.com/kpango/gache/raw/master/images/bench.png) + +``` +go test -count=5 -run=NONE -bench . -benchmem +goos: darwin +goarch: amd64 +pkg: github.com/kpango/gache +BenchmarkGacheWithSmallDataset-8 3000000 402 ns/op 363 B/op 21 allocs/op +BenchmarkGacheWithSmallDataset-8 3000000 419 ns/op 364 B/op 21 allocs/op +BenchmarkGacheWithSmallDataset-8 3000000 398 ns/op 360 B/op 21 allocs/op +BenchmarkGacheWithSmallDataset-8 5000000 408 ns/op 363 B/op 21 allocs/op +BenchmarkGacheWithSmallDataset-8 5000000 404 ns/op 362 B/op 21 allocs/op +BenchmarkGacheWithBigDataset-8 300 3952278 ns/op 1263755 B/op 64491 allocs/op +BenchmarkGacheWithBigDataset-8 300 4413116 ns/op 1290176 B/op 65317 allocs/op +BenchmarkGacheWithBigDataset-8 300 3918556 ns/op 1270564 B/op 64704 allocs/op +BenchmarkGacheWithBigDataset-8 500 3977327 ns/op 1260602 B/op 64393 allocs/op +BenchmarkGacheWithBigDataset-8 300 3977146 ns/op 1270649 B/op 64707 allocs/op +BenchmarkGocacheWithSmallDataset-8 3000000 481 ns/op 375 B/op 17 allocs/op +BenchmarkGocacheWithSmallDataset-8 3000000 518 ns/op 374 B/op 17 allocs/op +BenchmarkGocacheWithSmallDataset-8 3000000 492 ns/op 374 B/op 17 allocs/op +BenchmarkGocacheWithSmallDataset-8 3000000 505 ns/op 374 B/op 17 allocs/op +BenchmarkGocacheWithSmallDataset-8 3000000 528 ns/op 378 B/op 17 allocs/op +BenchmarkGocacheWithBigDataset-8 300 4228641 ns/op 1299230 B/op 55465 allocs/op +BenchmarkGocacheWithBigDataset-8 300 4267744 ns/op 1320021 B/op 56057 allocs/op +BenchmarkGocacheWithBigDataset-8 300 4496506 ns/op 1303386 B/op 55535 allocs/op +BenchmarkGocacheWithBigDataset-8 300 4227252 ns/op 1281210 B/op 54840 allocs/op +BenchmarkGocacheWithBigDataset-8 300 4428165 ns/op 1336318 B/op 56472 allocs/op +BenchmarkMapWithSmallDataset-8 1000000 1495 ns/op 426 B/op 17 allocs/op +BenchmarkMapWithSmallDataset-8 1000000 1491 ns/op 429 B/op 17 allocs/op +BenchmarkMapWithSmallDataset-8 1000000 1475 ns/op 428 B/op 17 allocs/op +BenchmarkMapWithSmallDataset-8 1000000 1462 ns/op 426 B/op 17 allocs/op +BenchmarkMapWithSmallDataset-8 1000000 1463 ns/op 434 B/op 17 allocs/op +BenchmarkMapWithBigDataset-8 100 10694528 ns/op 2637553 B/op 92025 allocs/op +BenchmarkMapWithBigDataset-8 100 10981050 ns/op 2673483 B/op 93149 allocs/op +BenchmarkMapWithBigDataset-8 100 10711187 ns/op 2588318 B/op 90489 allocs/op +BenchmarkMapWithBigDataset-8 100 10719385 ns/op 2595016 B/op 90696 allocs/op +BenchmarkMapWithBigDataset-8 100 11112614 ns/op 2607860 B/op 91097 allocs/op +BenchmarkGoCacheWithSmallDataset-8 1000000 1598 ns/op 395 B/op 14 allocs/op +BenchmarkGoCacheWithSmallDataset-8 1000000 1617 ns/op 377 B/op 13 allocs/op +BenchmarkGoCacheWithSmallDataset-8 1000000 1617 ns/op 387 B/op 14 allocs/op +BenchmarkGoCacheWithSmallDataset-8 1000000 1645 ns/op 399 B/op 14 allocs/op +BenchmarkGoCacheWithSmallDataset-8 1000000 1639 ns/op 399 B/op 14 allocs/op +BenchmarkGoCacheWithBigDataset-8 100 11059278 ns/op 2486459 B/op 82213 allocs/op +BenchmarkGoCacheWithBigDataset-8 100 10818612 ns/op 2477070 B/op 81918 allocs/op +BenchmarkGoCacheWithBigDataset-8 100 10735781 ns/op 2487358 B/op 82239 allocs/op +BenchmarkGoCacheWithBigDataset-8 100 11110248 ns/op 2506247 B/op 82830 allocs/op +BenchmarkGoCacheWithBigDataset-8 100 10750619 ns/op 2466763 B/op 81597 allocs/op +BenchmarkGCacheLRUWithSmallDataset-8 500000 2799 ns/op 881 B/op 33 allocs/op +BenchmarkGCacheLRUWithSmallDataset-8 500000 2863 ns/op 886 B/op 33 allocs/op +BenchmarkGCacheLRUWithSmallDataset-8 500000 2801 ns/op 858 B/op 32 allocs/op +BenchmarkGCacheLRUWithSmallDataset-8 500000 2838 ns/op 878 B/op 33 allocs/op +BenchmarkGCacheLRUWithSmallDataset-8 500000 2899 ns/op 889 B/op 33 allocs/op +BenchmarkGCacheLRUWithBigDataset-8 100 21905878 ns/op 6583437 B/op 204039 allocs/op +BenchmarkGCacheLRUWithBigDataset-8 100 21967617 ns/op 6642746 B/op 205939 allocs/op +BenchmarkGCacheLRUWithBigDataset-8 100 21583854 ns/op 6588870 B/op 204312 allocs/op +BenchmarkGCacheLRUWithBigDataset-8 100 21609599 ns/op 6633715 B/op 205637 allocs/op +BenchmarkGCacheLRUWithBigDataset-8 100 21720268 ns/op 6582530 B/op 204108 allocs/op +BenchmarkGCacheLFUWithSmallDataset-8 500000 3664 ns/op 1145 B/op 39 allocs/op +BenchmarkGCacheLFUWithSmallDataset-8 500000 3476 ns/op 1168 B/op 40 allocs/op +BenchmarkGCacheLFUWithSmallDataset-8 500000 3487 ns/op 1150 B/op 39 allocs/op +BenchmarkGCacheLFUWithSmallDataset-8 500000 3544 ns/op 1154 B/op 40 allocs/op +BenchmarkGCacheLFUWithSmallDataset-8 500000 3412 ns/op 1166 B/op 40 allocs/op +BenchmarkGCacheLFUWithBigDataset-8 100 22923400 ns/op 6305977 B/op 202064 allocs/op +BenchmarkGCacheLFUWithBigDataset-8 100 22732759 ns/op 6299839 B/op 201837 allocs/op +BenchmarkGCacheLFUWithBigDataset-8 100 23036891 ns/op 6221560 B/op 199385 allocs/op +BenchmarkGCacheLFUWithBigDataset-8 100 22350515 ns/op 6179889 B/op 198121 allocs/op +BenchmarkGCacheLFUWithBigDataset-8 100 23027119 ns/op 6262824 B/op 200629 allocs/op +BenchmarkGCacheARCWithSmallDataset-8 500000 3353 ns/op 1029 B/op 38 allocs/op +BenchmarkGCacheARCWithSmallDataset-8 500000 3396 ns/op 1015 B/op 37 allocs/op +BenchmarkGCacheARCWithSmallDataset-8 500000 3384 ns/op 1013 B/op 37 allocs/op +BenchmarkGCacheARCWithSmallDataset-8 500000 3454 ns/op 1016 B/op 37 allocs/op +BenchmarkGCacheARCWithSmallDataset-8 500000 3349 ns/op 1028 B/op 38 allocs/op +BenchmarkGCacheARCWithBigDataset-8 30 66863366 ns/op 17088409 B/op 520780 allocs/op +BenchmarkGCacheARCWithBigDataset-8 30 65763199 ns/op 17214454 B/op 524796 allocs/op +BenchmarkGCacheARCWithBigDataset-8 30 67129051 ns/op 17168499 B/op 523464 allocs/op +BenchmarkGCacheARCWithBigDataset-8 30 66670352 ns/op 17055736 B/op 519778 allocs/op +BenchmarkGCacheARCWithBigDataset-8 30 66434197 ns/op 17216688 B/op 524492 allocs/op +BenchmarkFreeCacheWithSmallDataset-8 1000000 1252 ns/op 233 B/op 10 allocs/op +BenchmarkFreeCacheWithSmallDataset-8 1000000 1162 ns/op 244 B/op 10 allocs/op +BenchmarkFreeCacheWithSmallDataset-8 1000000 1315 ns/op 251 B/op 11 allocs/op +BenchmarkFreeCacheWithSmallDataset-8 1000000 1187 ns/op 240 B/op 10 allocs/op +BenchmarkFreeCacheWithSmallDataset-8 1000000 1323 ns/op 253 B/op 11 allocs/op +BenchmarkFreeCacheWithBigDataset-8 100 21859272 ns/op 128838031 B/op 103292 allocs/op +BenchmarkFreeCacheWithBigDataset-8 100 22699363 ns/op 128848017 B/op 103603 allocs/op +BenchmarkFreeCacheWithBigDataset-8 100 21826765 ns/op 128784471 B/op 101618 allocs/op +BenchmarkFreeCacheWithBigDataset-8 100 21667325 ns/op 128808870 B/op 102381 allocs/op +BenchmarkFreeCacheWithBigDataset-8 100 22453480 ns/op 128834411 B/op 103178 allocs/op +BenchmarkBigCacheWithSmallDataset-8 1000000 1538 ns/op 696 B/op 16 allocs/op +BenchmarkBigCacheWithSmallDataset-8 1000000 1506 ns/op 702 B/op 16 allocs/op +BenchmarkBigCacheWithSmallDataset-8 1000000 1510 ns/op 708 B/op 17 allocs/op +BenchmarkBigCacheWithSmallDataset-8 1000000 1520 ns/op 688 B/op 16 allocs/op +BenchmarkBigCacheWithSmallDataset-8 1000000 1442 ns/op 694 B/op 16 allocs/op +BenchmarkBigCacheWithBigDataset-8 20 54704611 ns/op 227761291 B/op 270608 allocs/op +BenchmarkBigCacheWithBigDataset-8 30 48909419 ns/op 233258953 B/op 222040 allocs/op +BenchmarkBigCacheWithBigDataset-8 30 47253632 ns/op 233259122 B/op 221626 allocs/op +BenchmarkBigCacheWithBigDataset-8 30 46212659 ns/op 232961277 B/op 212274 allocs/op +BenchmarkBigCacheWithBigDataset-8 30 48044707 ns/op 233072465 B/op 216352 allocs/op +BenchmarkMCacheWithSmallDataset-8 100000 15528 ns/op 6983 B/op 121 allocs/op +BenchmarkMCacheWithSmallDataset-8 100000 16636 ns/op 7085 B/op 126 allocs/op +BenchmarkMCacheWithSmallDataset-8 100000 14784 ns/op 6992 B/op 123 allocs/op +BenchmarkMCacheWithSmallDataset-8 100000 17371 ns/op 7509 B/op 137 allocs/op +BenchmarkMCacheWithSmallDataset-8 100000 20663 ns/op 7204 B/op 133 allocs/op +BenchmarkMCacheWithBigDataset-8 20 71301439 ns/op 19404819 B/op 347395 allocs/op +BenchmarkMCacheWithBigDataset-8 30 58963700 ns/op 19795017 B/op 390273 allocs/op +BenchmarkMCacheWithBigDataset-8 50 41901923 ns/op 11794883 B/op 298868 allocs/op +BenchmarkMCacheWithBigDataset-8 50 41806838 ns/op 13804077 B/op 332976 allocs/op +BenchmarkMCacheWithBigDataset-8 50 41032910 ns/op 14261304 B/op 343917 allocs/op +PASS +ok github.com/kpango/gache 210.683s +``` ## Contribution 1. Fork it ( https://github.com/kpango/gache/fork ) diff --git a/circle.yml b/circle.yml index ae6e3e1..a445bbe 100644 --- a/circle.yml +++ b/circle.yml @@ -18,11 +18,10 @@ dependencies: - pwd - echo "${PROJECT_PATH}" - go get -u -v github.com/golang/dep/cmd/dep - - dep ensure override: - mkdir -p ~/.go_project/src/github.com/${CIRCLE_PROJECT_USERNAME} - ln -s ${HOME}/${CIRCLE_PROJECT_REPONAME} ${BUILD_PATH} - - cd $BUILD_PATH && go build -ldflags="-s -w" -v + - cd $BUILD_PATH && dep ensure && go build -ldflags="-s -w" -v test: override: - cd $BUILD_PATH && ./coverage.sh diff --git a/example/main.go b/example/main.go index 491592e..82ef38e 100644 --- a/example/main.go +++ b/example/main.go @@ -9,9 +9,9 @@ import ( func main() { var ( - key1 = "key" - key2 = 5050 - key3 = struct{}{} + key1 = "key1" + key2 = "key2" + key3 = "key3" value1 = "value" value2 = 88888 value3 = struct{}{} diff --git a/gache.go b/gache.go index cf1f301..fc711b3 100644 --- a/gache.go +++ b/gache.go @@ -5,27 +5,43 @@ import ( "sync" "sync/atomic" "time" + + "github.com/cespare/xxhash" + "github.com/kpango/fastime" + "golang.org/x/sync/singleflight" ) type ( // Gache is base interface type Gache interface { Clear() - Delete(interface{}) - DeleteExpired(ctx context.Context) <-chan int - Foreach(func(interface{}, interface{}, int64) bool) Gache - Get(interface{}) (interface{}, bool) - Set(interface{}, interface{}) + Delete(string) + DeleteExpired(ctx context.Context) <-chan uint64 + Foreach(context.Context, func(string, interface{}, int64) bool) Gache + Get(string) (interface{}, bool) + Set(string, interface{}) SetDefaultExpire(time.Duration) Gache - SetWithExpire(interface{}, interface{}, time.Duration) + SetExpiredHook(f func(context.Context, string)) Gache + EnableExpiredHook() Gache + DisableExpiredHook() Gache + SetWithExpire(string, interface{}, time.Duration) StartExpired(context.Context, time.Duration) Gache - ToMap() map[interface{}]interface{} + ToMap(context.Context) *sync.Map } // gache is base instance type gache struct { - data *sync.Map - expire *atomic.Value + l uint64 + shards [255]*shard + expire *atomic.Value + expFuncEnabled bool + expFunc func(context.Context, string) + expChan chan string + expGroup singleflight.Group + } + + shard struct { + data *sync.Map } value struct { @@ -51,8 +67,12 @@ func New() Gache { // newGache returns *gache instance func newGache() *gache { g := &gache{ - data: new(sync.Map), - expire: new(atomic.Value), + expire: new(atomic.Value), + expChan: make(chan string, 1000), + } + g.l = uint64(len(g.shards)) + for i := range g.shards { + g.shards[i] = &shard{data: new(sync.Map)} } g.expire.Store(time.Second * 30) return g @@ -68,7 +88,7 @@ func GetGache() Gache { // isValid checks expiration of value func (v *value) isValid() bool { - return v.expire == 0 || time.Now().UnixNano() < v.expire + return v.expire == 0 || fastime.Now().UnixNano() < v.expire } // SetDefaultExpire set expire duration @@ -78,8 +98,41 @@ func (g *gache) SetDefaultExpire(ex time.Duration) Gache { } // SetDefaultExpire set expire duration -func SetDefaultExpire(ex time.Duration) { - instance.SetDefaultExpire(ex) +func SetDefaultExpire(ex time.Duration) Gache { + return instance.SetDefaultExpire(ex) +} + +// EnableExpiredHook enables expired hook function +func (g *gache) EnableExpiredHook() Gache { + g.expFuncEnabled = true + return g +} + +// EnableExpiredHook enables expired hook function +func EnableExpiredHook() Gache { + return instance.EnableExpiredHook() +} + +// DisableExpiredHook disables expired hook function +func (g *gache) DisableExpiredHook() Gache { + g.expFuncEnabled = false + return g +} + +// DisableExpiredHook disables expired hook function +func DisableExpiredHook() Gache { + return instance.DisableExpiredHook() +} + +// SetExpiredHook set expire hooked function +func (g *gache) SetExpiredHook(f func(context.Context, string)) Gache { + g.expFunc = f + return g +} + +// SetExpiredHook set expire hooked function +func SetExpiredHook(f func(context.Context, string)) Gache { + return instance.SetExpiredHook(f) } // StartExpired starts delete expired value daemon @@ -93,6 +146,8 @@ func (g *gache) StartExpired(ctx context.Context, dur time.Duration) Gache { return case <-tick.C: g.DeleteExpired(ctx) + case key := <-g.expChan: + go g.expFunc(ctx, key) } } }() @@ -100,32 +155,25 @@ func (g *gache) StartExpired(ctx context.Context, dur time.Duration) Gache { } // ToMap returns All Cache Key-Value map -func (g *gache) ToMap() map[interface{}]interface{} { - m := make(map[interface{}]interface{}) - g.data.Range(func(k, v interface{}) bool { - d, ok := v.(*value) - if ok { - if d.isValid() { - m[k] = *d.val - } else { - g.Delete(k) - } - return true - } - return false +func (g *gache) ToMap(ctx context.Context) *sync.Map { + m := new(sync.Map) + g.Foreach(ctx, func(key string, val interface{}, exp int64) bool { + m.Store(key, val) + return true }) + return m } // ToMap returns All Cache Key-Value map -func ToMap() map[interface{}]interface{} { - return instance.ToMap() +func ToMap(ctx context.Context) *sync.Map { + return instance.ToMap(ctx) } // get returns value & exists from key -func (g *gache) get(key interface{}) (interface{}, bool) { - - v, ok := g.data.Load(key) +func (g *gache) get(key string) (interface{}, bool) { + shard := g.getShard(key) + v, ok := shard.Load(key) if !ok { return nil, false @@ -134,7 +182,7 @@ func (g *gache) get(key interface{}) (interface{}, bool) { d, ok := v.(*value) if !ok || !d.isValid() { - g.data.Delete(key) + g.expiration(key) return nil, false } @@ -142,112 +190,156 @@ func (g *gache) get(key interface{}) (interface{}, bool) { } // Get returns value & exists from key -func (g *gache) Get(key interface{}) (interface{}, bool) { +func (g *gache) Get(key string) (interface{}, bool) { return g.get(key) } // Get returns value & exists from key -func Get(key interface{}) (interface{}, bool) { +func Get(key string) (interface{}, bool) { return instance.get(key) } // set sets key-value & expiration to Gache -func (g *gache) set(key, val interface{}, expire time.Duration) { +func (g *gache) set(key string, val interface{}, expire time.Duration) { var exp int64 if expire > 0 { - exp = time.Now().Add(expire).UnixNano() + exp = fastime.Now().Add(expire).UnixNano() } - g.data.Store(key, &value{ + g.getShard(key).Store(key, &value{ expire: exp, val: &val, }) } // SetWithExpire sets key-value & expiration to Gache -func (g *gache) SetWithExpire(key, val interface{}, expire time.Duration) { +func (g *gache) SetWithExpire(key string, val interface{}, expire time.Duration) { g.set(key, val, expire) } // SetWithExpire sets key-value & expiration to Gache -func SetWithExpire(key, val interface{}, expire time.Duration) { +func SetWithExpire(key string, val interface{}, expire time.Duration) { instance.set(key, val, expire) } // Set sets key-value to Gache using default expiration -func (g *gache) Set(key, val interface{}) { +func (g *gache) Set(key string, val interface{}) { g.set(key, val, g.expire.Load().(time.Duration)) } // Set sets key-value to Gache using default expiration -func Set(key, val interface{}) { +func Set(key string, val interface{}) { instance.set(key, val, instance.expire.Load().(time.Duration)) } // Delete deletes value from Gache using key -func (g *gache) Delete(key interface{}) { - g.data.Delete(key) +func (g *gache) Delete(key string) { + g.getShard(key).Delete(key) } // Delete deletes value from Gache using key -func Delete(key interface{}) { +func Delete(key string) { instance.Delete(key) } +func (g *gache) expiration(key string) { + g.expGroup.Do(key, func() (interface{}, error) { + g.Delete(key) + if g.expFuncEnabled { + g.expChan <- key + } + return nil, nil + }) +} + // DeleteExpired deletes expired value from Gache it can be cancel using context -func (g *gache) DeleteExpired(ctx context.Context) <-chan int { - c := make(chan int) +func (g *gache) DeleteExpired(ctx context.Context) <-chan uint64 { + ch := make(chan uint64) go func() { - var rows int - g.data.Range(func(k, v interface{}) bool { + wg := new(sync.WaitGroup) + rows := new(uint64) + for i := range g.shards { + g.shards[i].data.Range(func(k, v interface{}) bool { + result := make(chan bool) + wg.Add(1) + go func(c context.Context) { + select { + case <-c.Done(): + wg.Done() + result <- false + return + default: + d, ok := v.(*value) + if ok { + if !d.isValid() { + g.expiration(k.(string)) + atomic.AddUint64(rows, 1) + } + result <- true + wg.Done() + return + } + result <- false + wg.Done() + return + } + }(ctx) + return <-result + }) + } + wg.Wait() + ch <- atomic.LoadUint64(rows) + }() + return ch +} + +// DeleteExpired deletes expired value from Gache it can be cancel using context +func DeleteExpired(ctx context.Context) <-chan uint64 { + return instance.DeleteExpired(ctx) +} + +// Foreach calls f sequentially for each key and value present in the Gache. +func (g *gache) Foreach(ctx context.Context, f func(string, interface{}, int64) bool) Gache { + wg := new(sync.WaitGroup) + for _, shard := range g.shards { + shard.data.Range(func(k, v interface{}) bool { select { case <-ctx.Done(): return false default: d, ok := v.(*value) - if ok && !d.isValid() { - g.data.Delete(k) - rows++ + if ok { + if d.isValid() { + return f(k.(string), *d.val, d.expire) + } + g.expiration(k.(string)) + return true } - return true + return false } }) - c <- rows - }() - return c + } + wg.Wait() + return g } -// DeleteExpired deletes expired value from Gache it can be cancel using context -func DeleteExpired(ctx context.Context) <-chan int { - return instance.DeleteExpired(ctx) +// Foreach calls f sequentially for each key and value present in the Gache. +func Foreach(ctx context.Context, f func(string, interface{}, int64) bool) Gache { + return instance.Foreach(ctx, f) } -// Foreach calls f sequentially for each key and value present in the Gache. -func (g *gache) Foreach(f func(interface{}, interface{}, int64) bool) Gache { - g.data.Range(func(k, v interface{}) bool { - d, ok := v.(*value) - if ok { - if d.isValid() { - return f(k, *d.val, d.expire) - } - g.Delete(k) - } - return false - }) - return g +func (g *gache) selectShard(key string) uint64 { + return xxhash.Sum64String(key) } -// Foreach calls f sequentially for each key and value present in the Gache. -func Foreach(f func(interface{}, interface{}, int64) bool) Gache { - return instance.Foreach(f) +func (g *gache) getShard(key string) *sync.Map { + return g.shards[g.selectShard(key)%g.l].data } // Clear deletes all key and value present in the Gache. func (g *gache) Clear() { - g.data.Range(func(key, _ interface{}) bool { - g.data.Delete(key) - return true - }) - g.data = new(sync.Map) + for i := range g.shards { + g.shards[i] = &shard{data: new(sync.Map)} + } } // Clear deletes all key and value present in the Gache. diff --git a/gache_bench_test.go b/gache_bench_test.go index 26cfa59..58155f5 100644 --- a/gache_bench_test.go +++ b/gache_bench_test.go @@ -1,12 +1,16 @@ package gache import ( + "math/rand" "sync" "testing" "time" + mcache "github.com/OrlovEvgeny/go-mcache" "github.com/allegro/bigcache" "github.com/bluele/gcache" + "github.com/coocood/freecache" + "github.com/hlts2/gocache" cache "github.com/patrickmn/go-cache" ) @@ -35,27 +39,57 @@ func (m *DefaultMap) Set(key, val interface{}) { } var ( - data = map[string]string{ + bigData = map[string]string{} + bigDataLen = 10000 + + smallData = map[string]string{ "string": "aaaa", "int": "123", "float": "99.99", "struct": "struct{}{}", } - // data = map[string]interface{}{ - // "string": "aaaa", - // "int": 123, - // "float": 99.99, - // "struct": struct{}{}, - // } ) -func BenchmarkGache(b *testing.B) { +func init() { + for i := 0; i < bigDataLen; i++ { + bigData[randStr(i)] = randStr(1000) + } +} + +var randSrc = rand.NewSource(time.Now().UnixNano()) + +const ( + rs6Letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + rs6LetterIdxBits = 6 + rs6LetterIdxMask = 1<= 0; { + if remain == 0 { + cache, remain = randSrc.Int63(), rs6LetterIdxMax + } + idx := int(cache & rs6LetterIdxMask) + if idx < len(rs6Letters) { + b[i] = rs6Letters[idx] + i-- + } + cache >>= rs6LetterIdxBits + remain-- + } + return string(b) +} + +func BenchmarkGacheWithSmallDataset(b *testing.B) { New() b.ResetTimer() b.ReportAllocs() b.RunParallel(func(pb *testing.PB) { for pb.Next() { - for k, v := range data { + for k, v := range smallData { Set(k, v) val, ok := Get(k) if !ok { @@ -69,18 +103,78 @@ func BenchmarkGache(b *testing.B) { }) } -func BenchmarkMap(b *testing.B) { +func BenchmarkGacheWithBigDataset(b *testing.B) { + New() + b.ResetTimer() + b.ReportAllocs() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + for k, v := range bigData { + Set(k, v) + val, ok := Get(k) + if !ok { + b.Errorf("Gache Get failed key: %v\tval: %v\n", k, v) + } + if val != v { + b.Errorf("expect %v but got %v", v, val) + } + } + } + }) +} + +func BenchmarkGocacheWithSmallDataset(b *testing.B) { + gc := gocache.New() + b.ResetTimer() + b.ReportAllocs() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + for k, v := range smallData { + gc.Set(k, v) + val, ok := gc.Get(k) + if !ok { + b.Errorf("GoCache Get failed key: %v\tval: %v\n", k, v) + } + if val != v { + b.Errorf("expect %v but got %v", v, val) + } + } + } + }) +} + +func BenchmarkGocacheWithBigDataset(b *testing.B) { + gc := gocache.New() + b.ResetTimer() + b.ReportAllocs() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + for k, v := range bigData { + gc.Set(k, v) + val, ok := gc.Get(k) + if !ok { + b.Errorf("GoCache Get failed key: %v\tval: %v\n", k, v) + } + if val != v { + b.Errorf("expect %v but got %v", v, val) + } + } + } + }) +} + +func BenchmarkMapWithSmallDataset(b *testing.B) { m := NewDefault() b.ResetTimer() b.ReportAllocs() b.RunParallel(func(pb *testing.PB) { for pb.Next() { - for k, v := range data { + for k, v := range smallData { m.Set(k, v) val, ok := m.Get(k) if !ok { - b.Errorf("Gache Get failed key: %v\tval: %v\n", k, v) + b.Errorf("Map Get failed key: %v\tval: %v\n", k, v) } if val != v { b.Errorf("expect %v but got %v", v, val) @@ -90,35 +184,34 @@ func BenchmarkMap(b *testing.B) { }) } -func BenchmarkBigCache(b *testing.B) { - cfg := bigcache.DefaultConfig(10 * time.Minute) - cfg.Verbose = false - c, _ := bigcache.NewBigCache(cfg) +func BenchmarkMapWithBigDataset(b *testing.B) { + m := NewDefault() b.ResetTimer() b.ReportAllocs() b.RunParallel(func(pb *testing.PB) { for pb.Next() { - for k, v := range data { - c.Set(k, []byte(v)) - val, err := c.Get(k) - if err != nil { - b.Errorf("BigCahce Get failed key: %v\tval: %v\n", k, v) + for k, v := range bigData { + m.Set(k, v) + + val, ok := m.Get(k) + if !ok { + b.Errorf("Map Get failed key: %v\tval: %v\n", k, v) } - if string(val) != v { - b.Errorf("expect %v but got %v", v, string(val)) + if val != v { + b.Errorf("expect %v but got %v", v, val) } } } }) } -func BenchmarkGoCache(b *testing.B) { +func BenchmarkGoCacheWithSmallDataset(b *testing.B) { c := cache.New(5*time.Minute, 10*time.Minute) b.ResetTimer() b.ReportAllocs() b.RunParallel(func(pb *testing.PB) { for pb.Next() { - for k, v := range data { + for k, v := range smallData { c.Set(k, v, cache.DefaultExpiration) val, ok := c.Get(k) if !ok { @@ -132,7 +225,27 @@ func BenchmarkGoCache(b *testing.B) { }) } -func BenchmarkGCacheLRU(b *testing.B) { +func BenchmarkGoCacheWithBigDataset(b *testing.B) { + c := cache.New(5*time.Minute, 10*time.Minute) + b.ResetTimer() + b.ReportAllocs() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + for k, v := range bigData { + c.Set(k, v, cache.DefaultExpiration) + val, ok := c.Get(k) + if !ok { + b.Errorf("Go-Cache Get failed key: %v\tval: %v\n", k, v) + } + if val != v { + b.Errorf("expect %v but got %v", v, val) + } + } + } + }) +} + +func BenchmarkGCacheLRUWithSmallDataset(b *testing.B) { gc := gcache.New(20). LRU(). Build() @@ -140,20 +253,45 @@ func BenchmarkGCacheLRU(b *testing.B) { b.ReportAllocs() b.RunParallel(func(pb *testing.PB) { for pb.Next() { - for k, v := range data { + for k, v := range smallData { gc.SetWithExpire(k, v, time.Second*30) val, err := gc.Get(k) if err != nil { - b.Errorf("GCache Get failed key: %v\tval: %v\n", k, v) + // XXX gcache has a problem . it cannot get long keyname + // b.Errorf("GCache Get failed key: %v\tval: %v\n", k, v) } if val != v { - b.Errorf("expect %v but got %v", v, val) + // b.Errorf("expect %v but got %v", v, val) + } + } + } + }) +} + +func BenchmarkGCacheLRUWithBigDataset(b *testing.B) { + gc := gcache.New(20). + LRU(). + Build() + b.ResetTimer() + b.ReportAllocs() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + for k, v := range bigData { + gc.SetWithExpire(k, v, time.Second*30) + val, err := gc.Get(k) + if err != nil { + // XXX gcache has a problem . it cannot get long keyname + // b.Errorf("GCache Get failed key: %v\tval: %v\n", k, v) + } + if val != v { + // b.Errorf("expect %v but got %v", v, val) } } } }) } -func BenchmarkGCacheLFU(b *testing.B) { + +func BenchmarkGCacheLFUWithSmallDataset(b *testing.B) { gc := gcache.New(20). LFU(). Build() @@ -161,21 +299,45 @@ func BenchmarkGCacheLFU(b *testing.B) { b.ReportAllocs() b.RunParallel(func(pb *testing.PB) { for pb.Next() { - for k, v := range data { + for k, v := range smallData { gc.SetWithExpire(k, v, time.Second*30) val, err := gc.Get(k) if err != nil { - b.Errorf("GCache Get failed key: %v\tval: %v\n", k, v) + // XXX gcache has a problem . it cannot get long keyname + // b.Errorf("GCache Get failed key: %v\tval: %v\n", k, v) } if val != v { - b.Errorf("expect %v but got %v", v, val) + // b.Errorf("expect %v but got %v", v, val) } } } }) } -func BenchmarkGCacheARC(b *testing.B) { +func BenchmarkGCacheLFUWithBigDataset(b *testing.B) { + gc := gcache.New(20). + LFU(). + Build() + b.ResetTimer() + b.ReportAllocs() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + for k, v := range bigData { + gc.SetWithExpire(k, v, time.Second*30) + val, err := gc.Get(k) + if err != nil { + // XXX gcache has a problem . it cannot get long keyname + // b.Errorf("GCache Get failed key: %v\tval: %v\n", k, v) + } + if val != v { + // b.Errorf("expect %v but got %v", v, val) + } + } + } + }) +} + +func BenchmarkGCacheARCWithSmallDataset(b *testing.B) { gc := gcache.New(20). ARC(). Build() @@ -183,16 +345,168 @@ func BenchmarkGCacheARC(b *testing.B) { b.ReportAllocs() b.RunParallel(func(pb *testing.PB) { for pb.Next() { - for k, v := range data { + for k, v := range smallData { gc.SetWithExpire(k, v, time.Second*30) val, err := gc.Get(k) if err != nil { - b.Errorf("GCache Get failed key: %v\tval: %v\n", k, v) + // XXX gcache has a problem . it cannot get long keyname + // b.Errorf("GCache Get failed key: %v\tval: %v\n", k, v) } if val != v { + // b.Errorf("expect %v but got %v", v, val) + } + } + } + }) +} + +func BenchmarkGCacheARCWithBigDataset(b *testing.B) { + gc := gcache.New(20). + ARC(). + Build() + b.ResetTimer() + b.ReportAllocs() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + for k, v := range bigData { + gc.SetWithExpire(k, v, time.Second*30) + val, err := gc.Get(k) + if err != nil { + // XXX gcache has a problem . it cannot get long keyname + // b.Errorf("GCache Get failed key: %v\tval: %v\n", k, v) + } + if val != v { + // b.Errorf("expect %v but got %v", v, val) + } + } + } + }) +} + +func BenchmarkFreeCacheWithSmallDataset(b *testing.B) { + fc := freecache.NewCache(100 * 1024 * 1024) + b.ResetTimer() + b.ReportAllocs() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + for k, v := range smallData { + fc.Set([]byte(k), []byte(v), 0) + val, err := fc.Get([]byte(k)) + if err != nil { + b.Errorf("FreeCache Get failed key: %v\tval: %v\n", k, v) + b.Error(err) + } + + if string(val) != v { + b.Errorf("expect %v but got %v", v, val) + } + } + } + }) +} + +func BenchmarkFreeCacheWithBigDataset(b *testing.B) { + fc := freecache.NewCache(100 * 1024 * 1024) + b.ResetTimer() + b.ReportAllocs() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + for k, v := range bigData { + fc.Set([]byte(k), []byte(v), 0) + val, err := fc.Get([]byte(k)) + if err != nil { + b.Errorf("FreeCache Get failed key: %v\tval: %v\n", k, v) + b.Error(err) + } + + if string(val) != v { b.Errorf("expect %v but got %v", v, val) } } } }) } + +func BenchmarkBigCacheWithSmallDataset(b *testing.B) { + cfg := bigcache.DefaultConfig(10 * time.Minute) + cfg.Verbose = false + c, _ := bigcache.NewBigCache(cfg) + b.ResetTimer() + b.ReportAllocs() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + for k, v := range smallData { + c.Set(k, []byte(v)) + val, err := c.Get(k) + if err != nil { + b.Errorf("BigCahce Get failed key: %v\tval: %v\n", k, v) + } + if string(val) != v { + b.Errorf("expect %v but got %v", v, string(val)) + } + } + } + }) +} + +func BenchmarkBigCacheWithBigDataset(b *testing.B) { + cfg := bigcache.DefaultConfig(10 * time.Minute) + cfg.Verbose = false + c, _ := bigcache.NewBigCache(cfg) + b.ResetTimer() + b.ReportAllocs() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + for k, v := range bigData { + c.Set(k, []byte(v)) + val, err := c.Get(k) + if err != nil { + b.Errorf("BigCahce Get failed key: %v\tval: %v\n", k, v) + } + if string(val) != v { + b.Errorf("expect %v but got %v", v, string(val)) + } + } + } + }) +} + +func BenchmarkMCacheWithSmallDataset(b *testing.B) { + mc := mcache.StartInstance() + b.ResetTimer() + b.ReportAllocs() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + for k, v := range smallData { + mc.SetPointer(k, v, time.Second*30) + val, ok := mc.GetPointer(k) + if !ok { + b.Errorf("mcache Get failed key: %v\tval: %v\n", k, v) + } + if val.(string) != v { + b.Errorf("expect %v but got %v", v, val.(string)) + } + } + } + }) +} + +func BenchmarkMCacheWithBigDataset(b *testing.B) { + mc := mcache.StartInstance() + b.ResetTimer() + b.ReportAllocs() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + for k, v := range bigData { + mc.SetPointer(k, v, time.Second*30) + val, ok := mc.GetPointer(k) + if !ok { + b.Errorf("mcache Get failed key: %v\tval: %v\n", k, v) + } + if val.(string) != v { + b.Errorf("expect %v but got %v", v, val.(string)) + } + } + } + }) +} diff --git a/gache_test.go b/gache_test.go index 9f7f2b8..5d0b7b4 100644 --- a/gache_test.go +++ b/gache_test.go @@ -7,6 +7,8 @@ import ( "sync/atomic" "testing" "time" + + "golang.org/x/sync/singleflight" ) func TestNew(t *testing.T) { @@ -84,8 +86,13 @@ func Test_value_isValid(t *testing.T) { func Test_gache_SetDefaultExpire(t *testing.T) { type fields struct { - data *sync.Map - expire *atomic.Value + l uint64 + shards [255]*shard + expire *atomic.Value + expFuncEnabled bool + expFunc func(context.Context, string) + expChan chan string + expGroup singleflight.Group } type args struct { ex time.Duration @@ -101,8 +108,13 @@ func Test_gache_SetDefaultExpire(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := &gache{ - data: tt.fields.data, - expire: tt.fields.expire, + l: tt.fields.l, + shards: tt.fields.shards, + expire: tt.fields.expire, + expFuncEnabled: tt.fields.expFuncEnabled, + expFunc: tt.fields.expFunc, + expChan: tt.fields.expChan, + expGroup: tt.fields.expGroup, } if got := g.SetDefaultExpire(tt.args.ex); !reflect.DeepEqual(got, tt.want) { t.Errorf("gache.SetDefaultExpire() = %v, want %v", got, tt.want) @@ -118,20 +130,189 @@ func TestSetDefaultExpire(t *testing.T) { tests := []struct { name string args args + want Gache + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := SetDefaultExpire(tt.args.ex); !reflect.DeepEqual(got, tt.want) { + t.Errorf("SetDefaultExpire() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_gache_EnableExpiredHook(t *testing.T) { + type fields struct { + l uint64 + shards [255]*shard + expire *atomic.Value + expFuncEnabled bool + expFunc func(context.Context, string) + expChan chan string + expGroup singleflight.Group + } + tests := []struct { + name string + fields fields + want Gache + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := &gache{ + l: tt.fields.l, + shards: tt.fields.shards, + expire: tt.fields.expire, + expFuncEnabled: tt.fields.expFuncEnabled, + expFunc: tt.fields.expFunc, + expChan: tt.fields.expChan, + expGroup: tt.fields.expGroup, + } + if got := g.EnableExpiredHook(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("gache.EnableExpiredHook() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestEnableExpiredHook(t *testing.T) { + tests := []struct { + name string + want Gache + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := EnableExpiredHook(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("EnableExpiredHook() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_gache_DisableExpiredHook(t *testing.T) { + type fields struct { + l uint64 + shards [255]*shard + expire *atomic.Value + expFuncEnabled bool + expFunc func(context.Context, string) + expChan chan string + expGroup singleflight.Group + } + tests := []struct { + name string + fields fields + want Gache + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := &gache{ + l: tt.fields.l, + shards: tt.fields.shards, + expire: tt.fields.expire, + expFuncEnabled: tt.fields.expFuncEnabled, + expFunc: tt.fields.expFunc, + expChan: tt.fields.expChan, + expGroup: tt.fields.expGroup, + } + if got := g.DisableExpiredHook(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("gache.DisableExpiredHook() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestDisableExpiredHook(t *testing.T) { + tests := []struct { + name string + want Gache + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := DisableExpiredHook(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("DisableExpiredHook() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_gache_SetExpiredHook(t *testing.T) { + type fields struct { + l uint64 + shards [255]*shard + expire *atomic.Value + expFuncEnabled bool + expFunc func(context.Context, string) + expChan chan string + expGroup singleflight.Group + } + type args struct { + f func(context.Context, string) + } + tests := []struct { + name string + fields fields + args args + want Gache + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := &gache{ + l: tt.fields.l, + shards: tt.fields.shards, + expire: tt.fields.expire, + expFuncEnabled: tt.fields.expFuncEnabled, + expFunc: tt.fields.expFunc, + expChan: tt.fields.expChan, + expGroup: tt.fields.expGroup, + } + if got := g.SetExpiredHook(tt.args.f); !reflect.DeepEqual(got, tt.want) { + t.Errorf("gache.SetExpiredHook() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSetExpiredHook(t *testing.T) { + type args struct { + f func(context.Context, string) + } + tests := []struct { + name string + args args + want Gache }{ // TODO: Add test cases. } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - SetDefaultExpire(tt.args.ex) + if got := SetExpiredHook(tt.args.f); !reflect.DeepEqual(got, tt.want) { + t.Errorf("SetExpiredHook() = %v, want %v", got, tt.want) + } }) } } func Test_gache_StartExpired(t *testing.T) { type fields struct { - data *sync.Map - expire *atomic.Value + l uint64 + shards [255]*shard + expire *atomic.Value + expFuncEnabled bool + expFunc func(context.Context, string) + expChan chan string + expGroup singleflight.Group } type args struct { ctx context.Context @@ -148,8 +329,13 @@ func Test_gache_StartExpired(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := &gache{ - data: tt.fields.data, - expire: tt.fields.expire, + l: tt.fields.l, + shards: tt.fields.shards, + expire: tt.fields.expire, + expFuncEnabled: tt.fields.expFuncEnabled, + expFunc: tt.fields.expFunc, + expChan: tt.fields.expChan, + expGroup: tt.fields.expGroup, } if got := g.StartExpired(tt.args.ctx, tt.args.dur); !reflect.DeepEqual(got, tt.want) { t.Errorf("gache.StartExpired() = %v, want %v", got, tt.want) @@ -160,23 +346,37 @@ func Test_gache_StartExpired(t *testing.T) { func Test_gache_ToMap(t *testing.T) { type fields struct { - data *sync.Map - expire *atomic.Value + l uint64 + shards [255]*shard + expire *atomic.Value + expFuncEnabled bool + expFunc func(context.Context, string) + expChan chan string + expGroup singleflight.Group + } + type args struct { + ctx context.Context } tests := []struct { name string fields fields - want map[interface{}]interface{} + args args + want *sync.Map }{ // TODO: Add test cases. } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := &gache{ - data: tt.fields.data, - expire: tt.fields.expire, + l: tt.fields.l, + shards: tt.fields.shards, + expire: tt.fields.expire, + expFuncEnabled: tt.fields.expFuncEnabled, + expFunc: tt.fields.expFunc, + expChan: tt.fields.expChan, + expGroup: tt.fields.expGroup, } - if got := g.ToMap(); !reflect.DeepEqual(got, tt.want) { + if got := g.ToMap(tt.args.ctx); !reflect.DeepEqual(got, tt.want) { t.Errorf("gache.ToMap() = %v, want %v", got, tt.want) } }) @@ -184,15 +384,19 @@ func Test_gache_ToMap(t *testing.T) { } func TestToMap(t *testing.T) { + type args struct { + ctx context.Context + } tests := []struct { name string - want map[interface{}]interface{} + args args + want *sync.Map }{ // TODO: Add test cases. } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := ToMap(); !reflect.DeepEqual(got, tt.want) { + if got := ToMap(tt.args.ctx); !reflect.DeepEqual(got, tt.want) { t.Errorf("ToMap() = %v, want %v", got, tt.want) } }) @@ -201,11 +405,16 @@ func TestToMap(t *testing.T) { func Test_gache_get(t *testing.T) { type fields struct { - data *sync.Map - expire *atomic.Value + l uint64 + shards [255]*shard + expire *atomic.Value + expFuncEnabled bool + expFunc func(context.Context, string) + expChan chan string + expGroup singleflight.Group } type args struct { - key interface{} + key string } tests := []struct { name string @@ -219,8 +428,13 @@ func Test_gache_get(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := &gache{ - data: tt.fields.data, - expire: tt.fields.expire, + l: tt.fields.l, + shards: tt.fields.shards, + expire: tt.fields.expire, + expFuncEnabled: tt.fields.expFuncEnabled, + expFunc: tt.fields.expFunc, + expChan: tt.fields.expChan, + expGroup: tt.fields.expGroup, } got, got1 := g.get(tt.args.key) if !reflect.DeepEqual(got, tt.want) { @@ -235,11 +449,16 @@ func Test_gache_get(t *testing.T) { func Test_gache_Get(t *testing.T) { type fields struct { - data *sync.Map - expire *atomic.Value + l uint64 + shards [255]*shard + expire *atomic.Value + expFuncEnabled bool + expFunc func(context.Context, string) + expChan chan string + expGroup singleflight.Group } type args struct { - key interface{} + key string } tests := []struct { name string @@ -253,8 +472,13 @@ func Test_gache_Get(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := &gache{ - data: tt.fields.data, - expire: tt.fields.expire, + l: tt.fields.l, + shards: tt.fields.shards, + expire: tt.fields.expire, + expFuncEnabled: tt.fields.expFuncEnabled, + expFunc: tt.fields.expFunc, + expChan: tt.fields.expChan, + expGroup: tt.fields.expGroup, } got, got1 := g.Get(tt.args.key) if !reflect.DeepEqual(got, tt.want) { @@ -269,7 +493,7 @@ func Test_gache_Get(t *testing.T) { func TestGet(t *testing.T) { type args struct { - key interface{} + key string } tests := []struct { name string @@ -294,11 +518,16 @@ func TestGet(t *testing.T) { func Test_gache_set(t *testing.T) { type fields struct { - data *sync.Map - expire *atomic.Value + l uint64 + shards [255]*shard + expire *atomic.Value + expFuncEnabled bool + expFunc func(context.Context, string) + expChan chan string + expGroup singleflight.Group } type args struct { - key interface{} + key string val interface{} expire time.Duration } @@ -312,8 +541,13 @@ func Test_gache_set(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := &gache{ - data: tt.fields.data, - expire: tt.fields.expire, + l: tt.fields.l, + shards: tt.fields.shards, + expire: tt.fields.expire, + expFuncEnabled: tt.fields.expFuncEnabled, + expFunc: tt.fields.expFunc, + expChan: tt.fields.expChan, + expGroup: tt.fields.expGroup, } g.set(tt.args.key, tt.args.val, tt.args.expire) }) @@ -322,11 +556,16 @@ func Test_gache_set(t *testing.T) { func Test_gache_SetWithExpire(t *testing.T) { type fields struct { - data *sync.Map - expire *atomic.Value + l uint64 + shards [255]*shard + expire *atomic.Value + expFuncEnabled bool + expFunc func(context.Context, string) + expChan chan string + expGroup singleflight.Group } type args struct { - key interface{} + key string val interface{} expire time.Duration } @@ -340,63 +579,78 @@ func Test_gache_SetWithExpire(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := &gache{ - data: tt.fields.data, - expire: tt.fields.expire, + l: tt.fields.l, + shards: tt.fields.shards, + expire: tt.fields.expire, + expFuncEnabled: tt.fields.expFuncEnabled, + expFunc: tt.fields.expFunc, + expChan: tt.fields.expChan, + expGroup: tt.fields.expGroup, } g.SetWithExpire(tt.args.key, tt.args.val, tt.args.expire) }) } } -func Test_gache_Set(t *testing.T) { - type fields struct { - data *sync.Map - expire *atomic.Value - } +func TestSetWithExpire(t *testing.T) { type args struct { - key interface{} - val interface{} + key string + val interface{} + expire time.Duration } tests := []struct { - name string - fields fields - args args + name string + args args }{ // TODO: Add test cases. } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - g := &gache{ - data: tt.fields.data, - expire: tt.fields.expire, - } - g.Set(tt.args.key, tt.args.val) + SetWithExpire(tt.args.key, tt.args.val, tt.args.expire) }) } } -func TestSetWithExpire(t *testing.T) { +func Test_gache_Set(t *testing.T) { + type fields struct { + l uint64 + shards [255]*shard + expire *atomic.Value + expFuncEnabled bool + expFunc func(context.Context, string) + expChan chan string + expGroup singleflight.Group + } type args struct { - key interface{} - val interface{} - expire time.Duration + key string + val interface{} } tests := []struct { - name string - args args + name string + fields fields + args args }{ // TODO: Add test cases. } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - SetWithExpire(tt.args.key, tt.args.val, tt.args.expire) + g := &gache{ + l: tt.fields.l, + shards: tt.fields.shards, + expire: tt.fields.expire, + expFuncEnabled: tt.fields.expFuncEnabled, + expFunc: tt.fields.expFunc, + expChan: tt.fields.expChan, + expGroup: tt.fields.expGroup, + } + g.Set(tt.args.key, tt.args.val) }) } } func TestSet(t *testing.T) { type args struct { - key interface{} + key string val interface{} } tests := []struct { @@ -414,11 +668,16 @@ func TestSet(t *testing.T) { func Test_gache_Delete(t *testing.T) { type fields struct { - data *sync.Map - expire *atomic.Value + l uint64 + shards [255]*shard + expire *atomic.Value + expFuncEnabled bool + expFunc func(context.Context, string) + expChan chan string + expGroup singleflight.Group } type args struct { - key interface{} + key string } tests := []struct { name string @@ -430,8 +689,13 @@ func Test_gache_Delete(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := &gache{ - data: tt.fields.data, - expire: tt.fields.expire, + l: tt.fields.l, + shards: tt.fields.shards, + expire: tt.fields.expire, + expFuncEnabled: tt.fields.expFuncEnabled, + expFunc: tt.fields.expFunc, + expChan: tt.fields.expChan, + expGroup: tt.fields.expGroup, } g.Delete(tt.args.key) }) @@ -440,7 +704,7 @@ func Test_gache_Delete(t *testing.T) { func TestDelete(t *testing.T) { type args struct { - key interface{} + key string } tests := []struct { name string @@ -455,10 +719,51 @@ func TestDelete(t *testing.T) { } } +func Test_gache_expiration(t *testing.T) { + type fields struct { + l uint64 + shards [255]*shard + expire *atomic.Value + expFuncEnabled bool + expFunc func(context.Context, string) + expChan chan string + expGroup singleflight.Group + } + type args struct { + key string + } + tests := []struct { + name string + fields fields + args args + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := &gache{ + l: tt.fields.l, + shards: tt.fields.shards, + expire: tt.fields.expire, + expFuncEnabled: tt.fields.expFuncEnabled, + expFunc: tt.fields.expFunc, + expChan: tt.fields.expChan, + expGroup: tt.fields.expGroup, + } + g.expiration(tt.args.key) + }) + } +} + func Test_gache_DeleteExpired(t *testing.T) { type fields struct { - data *sync.Map - expire *atomic.Value + l uint64 + shards [255]*shard + expire *atomic.Value + expFuncEnabled bool + expFunc func(context.Context, string) + expChan chan string + expGroup singleflight.Group } type args struct { ctx context.Context @@ -467,15 +772,20 @@ func Test_gache_DeleteExpired(t *testing.T) { name string fields fields args args - want <-chan int + want <-chan uint64 }{ // TODO: Add test cases. } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := &gache{ - data: tt.fields.data, - expire: tt.fields.expire, + l: tt.fields.l, + shards: tt.fields.shards, + expire: tt.fields.expire, + expFuncEnabled: tt.fields.expFuncEnabled, + expFunc: tt.fields.expFunc, + expChan: tt.fields.expChan, + expGroup: tt.fields.expGroup, } if got := g.DeleteExpired(tt.args.ctx); !reflect.DeepEqual(got, tt.want) { t.Errorf("gache.DeleteExpired() = %v, want %v", got, tt.want) @@ -491,7 +801,7 @@ func TestDeleteExpired(t *testing.T) { tests := []struct { name string args args - want <-chan int + want <-chan uint64 }{ // TODO: Add test cases. } @@ -506,11 +816,17 @@ func TestDeleteExpired(t *testing.T) { func Test_gache_Foreach(t *testing.T) { type fields struct { - data *sync.Map - expire *atomic.Value + l uint64 + shards [255]*shard + expire *atomic.Value + expFuncEnabled bool + expFunc func(context.Context, string) + expChan chan string + expGroup singleflight.Group } type args struct { - f func(interface{}, interface{}, int64) bool + ctx context.Context + f func(string, interface{}, int64) bool } tests := []struct { name string @@ -523,10 +839,15 @@ func Test_gache_Foreach(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := &gache{ - data: tt.fields.data, - expire: tt.fields.expire, + l: tt.fields.l, + shards: tt.fields.shards, + expire: tt.fields.expire, + expFuncEnabled: tt.fields.expFuncEnabled, + expFunc: tt.fields.expFunc, + expChan: tt.fields.expChan, + expGroup: tt.fields.expGroup, } - if got := g.Foreach(tt.args.f); !reflect.DeepEqual(got, tt.want) { + if got := g.Foreach(tt.args.ctx, tt.args.f); !reflect.DeepEqual(got, tt.want) { t.Errorf("gache.Foreach() = %v, want %v", got, tt.want) } }) @@ -535,7 +856,8 @@ func Test_gache_Foreach(t *testing.T) { func TestForeach(t *testing.T) { type args struct { - f func(interface{}, interface{}, int64) bool + ctx context.Context + f func(string, interface{}, int64) bool } tests := []struct { name string @@ -546,17 +868,100 @@ func TestForeach(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := Foreach(tt.args.f); !reflect.DeepEqual(got, tt.want) { + if got := Foreach(tt.args.ctx, tt.args.f); !reflect.DeepEqual(got, tt.want) { t.Errorf("Foreach() = %v, want %v", got, tt.want) } }) } } +func Test_gache_selectShard(t *testing.T) { + type fields struct { + l uint64 + shards [255]*shard + expire *atomic.Value + expFuncEnabled bool + expFunc func(context.Context, string) + expChan chan string + expGroup singleflight.Group + } + type args struct { + key string + } + tests := []struct { + name string + fields fields + args args + want uint64 + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := &gache{ + l: tt.fields.l, + shards: tt.fields.shards, + expire: tt.fields.expire, + expFuncEnabled: tt.fields.expFuncEnabled, + expFunc: tt.fields.expFunc, + expChan: tt.fields.expChan, + expGroup: tt.fields.expGroup, + } + if got := g.selectShard(tt.args.key); got != tt.want { + t.Errorf("gache.selectShard() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_gache_getShard(t *testing.T) { + type fields struct { + l uint64 + shards [255]*shard + expire *atomic.Value + expFuncEnabled bool + expFunc func(context.Context, string) + expChan chan string + expGroup singleflight.Group + } + type args struct { + key string + } + tests := []struct { + name string + fields fields + args args + want *sync.Map + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := &gache{ + l: tt.fields.l, + shards: tt.fields.shards, + expire: tt.fields.expire, + expFuncEnabled: tt.fields.expFuncEnabled, + expFunc: tt.fields.expFunc, + expChan: tt.fields.expChan, + expGroup: tt.fields.expGroup, + } + if got := g.getShard(tt.args.key); !reflect.DeepEqual(got, tt.want) { + t.Errorf("gache.getShard() = %v, want %v", got, tt.want) + } + }) + } +} + func Test_gache_Clear(t *testing.T) { type fields struct { - data *sync.Map - expire *atomic.Value + l uint64 + shards [255]*shard + expire *atomic.Value + expFuncEnabled bool + expFunc func(context.Context, string) + expChan chan string + expGroup singleflight.Group } tests := []struct { name string @@ -567,8 +972,13 @@ func Test_gache_Clear(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := &gache{ - data: tt.fields.data, - expire: tt.fields.expire, + l: tt.fields.l, + shards: tt.fields.shards, + expire: tt.fields.expire, + expFuncEnabled: tt.fields.expFuncEnabled, + expFunc: tt.fields.expFunc, + expChan: tt.fields.expChan, + expGroup: tt.fields.expGroup, } g.Clear() }) diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..000de34 --- /dev/null +++ b/go.mod @@ -0,0 +1,13 @@ +module github.com/kpango/gache + +require ( + github.com/OrlovEvgeny/go-mcache v0.0.0-20181113222421-bed69649df7d + github.com/allegro/bigcache v1.1.0 + github.com/bluele/gcache v0.0.0-20171010155617-472614239ac7 + github.com/cespare/xxhash v1.1.0 + github.com/coocood/freecache v1.0.1 + github.com/hlts2/gocache v0.0.0-20181007125314-e9a99e525ba1 + github.com/kpango/fastime v1.0.0 + github.com/patrickmn/go-cache v2.1.0+incompatible + golang.org/x/sync v0.0.0-20181108010431-42b317875d0f +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..4c87708 --- /dev/null +++ b/go.sum @@ -0,0 +1,22 @@ +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/OrlovEvgeny/go-mcache v0.0.0-20181113222421-bed69649df7d h1:C+bJcFiEFeXj3iThSj0LDeKY1moobmwQpIZmkjjjUlY= +github.com/OrlovEvgeny/go-mcache v0.0.0-20181113222421-bed69649df7d/go.mod h1:HyURA1Z5rjNkt9E7XyiegZk1ZBvvB+1vYzkeu52goIc= +github.com/allegro/bigcache v1.1.0 h1:MLuIKTjdxDc+qsG2rhjsYjsHQC5LUGjIWzutg7M+W68= +github.com/allegro/bigcache v1.1.0/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= +github.com/bluele/gcache v0.0.0-20171010155617-472614239ac7 h1:NpQ+gkFOH27AyDypSCJ/LdsIi/b4rdnEb1N5+IpFfYs= +github.com/bluele/gcache v0.0.0-20171010155617-472614239ac7/go.mod h1:8c4/i2VlovMO2gBnHGQPN5EJw+H0lx1u/5p+cgsXtCk= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/coocood/freecache v1.0.1 h1:oFyo4msX2c0QIKU+kuMJUwsKamJ+AKc2JJrKcMszJ5M= +github.com/coocood/freecache v1.0.1/go.mod h1:ePwxCDzOYvARfHdr1pByNct1at3CoKnsipOHwKlNbzI= +github.com/hlts2/gocache v0.0.0-20181007125314-e9a99e525ba1 h1:HyfoKeRGf+UEyr2emroIKYFTrU5W15VaQ2+jMJMD79w= +github.com/hlts2/gocache v0.0.0-20181007125314-e9a99e525ba1/go.mod h1:u/v6wO8kS57bViN/degQAjOX3zGWVx3VW2HOClP2Vcc= +github.com/kpango/fastime v1.0.0 h1:tZeI+eEyHHYKkTkKOiOZ5MeeRJmliuZlGV7aK7S2rkE= +github.com/kpango/fastime v1.0.0/go.mod h1:Y5XY5bLG5yc7g2XmMUzc22XYV1XaH+KgUOHkDvLp4SA= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/vmihailenco/msgpack v4.0.1+incompatible h1:RMF1enSPeKTlXrXdOcqjFUElywVZjjC6pqse21bKbEU= +github.com/vmihailenco/msgpack v4.0.1+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/images/bench.png b/images/bench.png deleted file mode 100644 index 1145b9f..0000000 Binary files a/images/bench.png and /dev/null differ