Skip to content

Commit

Permalink
抽取 now 和 hash 到缓存级别
Browse files Browse the repository at this point in the history
  • Loading branch information
FishGoddess committed Feb 5, 2023
1 parent a5faeb9 commit 9f1c115
Show file tree
Hide file tree
Showing 16 changed files with 211 additions and 60 deletions.
3 changes: 2 additions & 1 deletion FUTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@
* [x] 检查 pkg 代码,完善单元测试,提高覆盖率
* [x] 清理废话注释,完善 examples 和性能测试
* [x] 增加 report 机制用于监控缓存的情况
* [x] 提取 now 和 hash 到缓存级别配置
* [ ] 提供定时缓存时间的机制,可选快速时钟
* [ ] 增加对不存在的数据做防穿透的机制
* [ ] 提供一个清空并设置全量值的方法,方便定时数据的全量替换
* [ ] 提供定时缓存时间的 Clock 功能,可选标准库和快速两种

### v0.3.x

Expand Down
13 changes: 8 additions & 5 deletions entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,15 @@ type entry struct {
key string
value interface{}
expiration int64 // Time in nanosecond, valid util 2262 year (enough, uh?)
now func() int64
}

func newEntry(key string, value interface{}, ttl time.Duration) *entry {
e := new(entry)
e.setup(key, value, ttl)
func newEntry(key string, value interface{}, ttl time.Duration, now func() int64) *entry {
e := &entry{
now: now,
}

e.setup(key, value, ttl)
return e
}

Expand All @@ -35,7 +38,7 @@ func (e *entry) setup(key string, value interface{}, ttl time.Duration) {
e.expiration = 0

if ttl > 0 {
e.expiration = Now() + ttl.Nanoseconds()
e.expiration = e.now() + ttl.Nanoseconds()
}
}

Expand All @@ -44,5 +47,5 @@ func (e *entry) expired(now int64) bool {
return e.expiration > 0 && e.expiration < now
}

return e.expiration > 0 && e.expiration < Now()
return e.expiration > 0 && e.expiration < e.now()
}
17 changes: 13 additions & 4 deletions entry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@
package cachego

import (
"fmt"
"testing"
"time"
)

// go test -v -cover -run=^TestNewEntry$
func TestNewEntry(t *testing.T) {
e := newEntry("key", "value", 0)
e := newEntry("key", "value", 0, now)

if e.key != "key" {
t.Errorf("e.key %s is wrong", e.key)
Expand All @@ -35,7 +36,11 @@ func TestNewEntry(t *testing.T) {
t.Errorf("e.expiration %+v != 0", e.expiration)
}

e = newEntry("k", "v", time.Second)
if fmt.Sprintf("%p", e.now) != fmt.Sprintf("%p", now) {
t.Errorf("e.now %p is wrong", e.now)
}

e = newEntry("k", "v", time.Second, now)
expiration := time.Now().Add(time.Second).UnixNano()

if e.key != "k" {
Expand All @@ -50,6 +55,10 @@ func TestNewEntry(t *testing.T) {
t.Error("e.expiration == 0")
}

if fmt.Sprintf("%p", e.now) != fmt.Sprintf("%p", now) {
t.Errorf("e.now %p is wrong", e.now)
}

// Keep one us for code running.
if expiration < e.expiration || e.expiration < expiration-time.Microsecond.Nanoseconds() {
t.Errorf("e.expiration %d != expiration %d", e.expiration, expiration)
Expand All @@ -58,7 +67,7 @@ func TestNewEntry(t *testing.T) {

// go test -v -cover -run=^TestEntrySetup$
func TestEntrySetup(t *testing.T) {
e := newEntry("key", "value", 0)
e := newEntry("key", "value", 0, now)

if e.key != "key" {
t.Errorf("e.key %s is wrong", e.key)
Expand Down Expand Up @@ -100,7 +109,7 @@ func TestEntrySetup(t *testing.T) {

// go test -cover -run=^TestEntryExpired$
func TestEntryExpired(t *testing.T) {
e := newEntry("", nil, time.Millisecond)
e := newEntry("", nil, time.Millisecond, now)

if e.expired(0) {
t.Error("e should be unexpired!")
Expand Down
29 changes: 16 additions & 13 deletions global.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,8 @@ package cachego
import "time"

var (
// MapInitialCap is the initial capacity of map.
MapInitialCap = 64

// SliceInitialCap is the initial capacity of slice.
SliceInitialCap = 64
)

var (
// Hash returns the hash code of one key.
Hash = hash

// Now returns the current time in nanosecond.
Now = now
mapInitialCap = 64
sliceInitialCap = 64
)

func hash(key string) int {
Expand All @@ -46,3 +35,17 @@ func hash(key string) int {
func now() int64 {
return time.Now().UnixNano()
}

// SetMapInitialCap sets the initial capacity of map.
func SetMapInitialCap(initialCap int) {
if initialCap > 0 {
mapInitialCap = initialCap
}
}

// SetSliceInitialCap sets the initial capacity of slice.
func SetSliceInitialCap(initialCap int) {
if initialCap > 0 {
sliceInitialCap = initialCap
}
}
48 changes: 44 additions & 4 deletions global_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,24 +25,64 @@ func BenchmarkHash(b *testing.B) {
b.ResetTimer()

for i := 0; i < b.N; i++ {
Hash("key")
hash("key")
}
}

// go test -v -cover -run=^TestHash$
func TestHash(t *testing.T) {
hash := Hash("test")
if hash <= 0 {
hash := hash("test")
if hash < 0 {
t.Errorf("hash %d <= 0", hash)
}
}

// go test -v -cover -run=^TestNow$
func TestNow(t *testing.T) {
got := Now()
got := now()
expect := time.Now().UnixNano()

if got > expect || got < expect-time.Microsecond.Nanoseconds() {
t.Errorf("got %d != expect %d", got, expect)
}
}

// go test -v -cover -run=^TestSetMapInitialCap$
func TestSetMapInitialCap(t *testing.T) {
oldInitialCap := mapInitialCap

SetMapInitialCap(-2)
if mapInitialCap != oldInitialCap {
t.Errorf("mapInitialCap %d is wrong", mapInitialCap)
}

SetMapInitialCap(0)
if mapInitialCap != oldInitialCap {
t.Errorf("mapInitialCap %d is wrong", mapInitialCap)
}

SetMapInitialCap(2)
if mapInitialCap != 2 {
t.Errorf("mapInitialCap %d is wrong", mapInitialCap)
}
}

// go test -v -cover -run=^TestSetSliceInitialCap$
func TestSetSliceInitialCap(t *testing.T) {
oldInitialCap := sliceInitialCap

SetSliceInitialCap(-2)
if sliceInitialCap != oldInitialCap {
t.Errorf("sliceInitialCap %d is wrong", sliceInitialCap)
}

SetSliceInitialCap(0)
if sliceInitialCap != oldInitialCap {
t.Errorf("sliceInitialCap %d is wrong", sliceInitialCap)
}

SetSliceInitialCap(2)
if sliceInitialCap != 2 {
t.Errorf("sliceInitialCap %d is wrong", sliceInitialCap)
}
}
12 changes: 6 additions & 6 deletions lfu.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ func newLFUCache(conf *config) Cache {
}

cache := &lfuCache{
itemMap: make(map[string]*heap.Item, MapInitialCap),
itemHeap: heap.New(SliceInitialCap),
itemMap: make(map[string]*heap.Item, mapInitialCap),
itemHeap: heap.New(sliceInitialCap),
}

cache.setup(conf, cache)
Expand Down Expand Up @@ -87,7 +87,7 @@ func (lc *lfuCache) set(key string, value interface{}, ttl time.Duration) (evict
evictedValue = lc.evict()
}

item = lc.itemHeap.Push(0, newEntry(key, value, ttl))
item = lc.itemHeap.Push(0, newEntry(key, value, ttl, lc.now))
lc.itemMap[key] = item

return evictedValue
Expand Down Expand Up @@ -115,7 +115,7 @@ func (lc *lfuCache) size() (size int) {
}

func (lc *lfuCache) gc() (cleans int) {
now := Now()
now := lc.now()
scans := 0

for _, item := range lc.itemMap {
Expand All @@ -135,8 +135,8 @@ func (lc *lfuCache) gc() (cleans int) {
}

func (lc *lfuCache) reset() {
lc.itemMap = make(map[string]*heap.Item, MapInitialCap)
lc.itemHeap = heap.New(SliceInitialCap)
lc.itemMap = make(map[string]*heap.Item, mapInitialCap)
lc.itemHeap = heap.New(sliceInitialCap)
lc.Loader.Reset()
}

Expand Down
2 changes: 1 addition & 1 deletion load.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func NewLoader(cache Cache, enableSingleflight bool) Loader {
}

if enableSingleflight {
loader.group = singleflight.NewGroup(MapInitialCap)
loader.group = singleflight.NewGroup(mapInitialCap)
}

return loader
Expand Down
8 changes: 4 additions & 4 deletions lru.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func newLRUCache(conf *config) Cache {
}

cache := &lruCache{
elementMap: make(map[string]*list.Element, MapInitialCap),
elementMap: make(map[string]*list.Element, mapInitialCap),
elementList: list.New(),
}

Expand Down Expand Up @@ -86,7 +86,7 @@ func (lc *lruCache) set(key string, value interface{}, ttl time.Duration) (evict
evictedValue = lc.evict()
}

element = lc.elementList.PushFront(newEntry(key, value, ttl))
element = lc.elementList.PushFront(newEntry(key, value, ttl, lc.now))
lc.elementMap[key] = element

return evictedValue
Expand Down Expand Up @@ -114,7 +114,7 @@ func (lc *lruCache) size() (size int) {
}

func (lc *lruCache) gc() (cleans int) {
now := Now()
now := lc.now()
scans := 0

for _, element := range lc.elementMap {
Expand All @@ -134,7 +134,7 @@ func (lc *lruCache) gc() (cleans int) {
}

func (lc *lruCache) reset() {
lc.elementMap = make(map[string]*list.Element, MapInitialCap)
lc.elementMap = make(map[string]*list.Element, mapInitialCap)
lc.elementList = list.New()
lc.Loader.Reset()
}
Expand Down
25 changes: 25 additions & 0 deletions option.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ type config struct {
maxScans int
maxEntries int

now func() int64
hash func(key string) int

reportMissed func(key string)
reportHit func(key string, value interface{})
reportGC func(cost time.Duration, cleans int)
Expand All @@ -41,6 +44,8 @@ func newDefaultConfig() *config {
gcDuration: 0,
maxScans: 10000,
maxEntries: 0,
now: now,
hash: hash,
}
}

Expand Down Expand Up @@ -122,6 +127,26 @@ func WithMaxEntries(maxEntries int) Option {
}
}

// WithNow returns an option setting the now function of cache.
// A now function should return a nanosecond unix time.
func WithNow(now func() int64) Option {
return func(conf *config) {
if now != nil {
conf.now = now
}
}
}

// WithHash returns an option setting the hash function of cache.
// A hash function should return the hash code of key.
func WithHash(hash func(key string) int) Option {
return func(conf *config) {
if hash != nil {
conf.hash = hash
}
}
}

// WithReportMissed returns an option setting the reportMissed of cache.
func WithReportMissed(reportMissed func(key string)) Option {
return func(conf *config) {
Expand Down
38 changes: 38 additions & 0 deletions option_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ func isConfigEquals(conf1 *config, conf2 *config) bool {
return false
}

if fmt.Sprintf("%p", conf1.now) != fmt.Sprintf("%p", conf2.now) {
return false
}

if fmt.Sprintf("%p", conf1.hash) != fmt.Sprintf("%p", conf2.hash) {
return false
}

if fmt.Sprintf("%p", conf1.reportMissed) != fmt.Sprintf("%p", conf2.reportMissed) {
return false
}
Expand Down Expand Up @@ -172,6 +180,36 @@ func TestWithMaxEntries(t *testing.T) {
}
}

// go test -v -cover -run=^TestWithNow$
func TestWithNow(t *testing.T) {
now := func() int64 {
return 0
}

got := &config{now: nil}
expect := &config{now: now}

WithNow(now).applyTo(got)
if !isConfigEquals(got, expect) {
t.Errorf("got %+v != expect %+v", got, expect)
}
}

// go test -v -cover -run=^TestWithHash$
func TestWithHash(t *testing.T) {
hash := func(key string) int {
return 0
}

got := &config{hash: nil}
expect := &config{hash: hash}

WithHash(hash).applyTo(got)
if !isConfigEquals(got, expect) {
t.Errorf("got %+v != expect %+v", got, expect)
}
}

// go test -v -cover -run=^TestWithReportMissed$
func TestWithReportMissed(t *testing.T) {
reportMissed := func(key string) {}
Expand Down
Loading

0 comments on commit 9f1c115

Please sign in to comment.