From bdea62cade3c01000e3ca4b15540305af47fc1e6 Mon Sep 17 00:00:00 2001 From: zhanglei25 Date: Sat, 18 Jun 2022 12:16:14 +0800 Subject: [PATCH 1/4] add shard for arc --- arc_shard.go | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++ cache.go | 27 +++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 arc_shard.go diff --git a/arc_shard.go b/arc_shard.go new file mode 100644 index 0000000..f8b6bce --- /dev/null +++ b/arc_shard.go @@ -0,0 +1,97 @@ +package gcache + +import "time" + +type ARCShard struct { + shard []*ARC + baseCache +} + +//var _ ARCShard = *Cache() + +var _ Cache = (*ARCShard)(nil) + +func newARCShard(cb *CacheBuilder) *ARCShard { + c := &ARCShard{} + buildCache(&c.baseCache, cb) + c.init(cb) + //c.loadGroup.cache = c + + return c +} + +func (c *ARCShard) init(cb *CacheBuilder) { + c.shard = make([]*ARC, cb.shardCount) + for i := 0; i < c.shardCount; i++ { + c.shard[i] = newARC(cb) + } +} + +// GetShard returns shard under given key +func (c *ARCShard) getShard(key interface{}) *ARC { + return c.shard[uint(fnv32(key))%uint(c.shardCount)] +} + +func (c *ARCShard) Set(key, value interface{}) error { + arc := c.getShard(key) + _, err := arc.set(key, value) + return err +} + +func (c *ARCShard) SetWithExpire(key, value interface{}, expiration time.Duration) error { + arc := c.getShard(key) + return arc.SetWithExpire(key, value, expiration) +} + +func (c *ARCShard) Get(key interface{}) (interface{}, error) { + arc := c.getShard(key) + v, err := arc.Get(key) + return v, err +} + +func (c *ARCShard) GetIFPresent(key interface{}) (interface{}, error) { + arc := c.getShard(key) + return arc.GetIFPresent(key) +} + +func (c *ARCShard) GetALL(checkExpired bool) map[interface{}]interface{} { + //todo + //return arc.GetALL(checkExpired) + return map[interface{}]interface{}{} +} + +func (c *ARCShard) get(key interface{}, onLoad bool) (interface{}, error) { + arc := c.getShard(key) + return arc.get(key, onLoad) +} + +func (c *ARCShard) Remove(key interface{}) bool { + arc := c.getShard(key) + return arc.Remove(key) +} + +func (c *ARCShard) Purge() { + for _, arc := range c.shard { + arc.Purge() + } + return +} + +func (c *ARCShard) Keys(checkExpired bool) []interface{} { + allKeys := make([]interface{}, 0) + for _, arc := range c.shard { + keys := arc.Keys(checkExpired) + allKeys = append(allKeys, keys) + } + return allKeys +} + +func (c *ARCShard) Len(checkExpired bool) int { + //TODO + return 0 +} + +func (c *ARCShard) Has(key interface{}) bool { + //TODO + return false +} diff --git a/cache.go b/cache.go index 0ae51bf..497b433 100644 --- a/cache.go +++ b/cache.go @@ -51,6 +51,7 @@ type Cache interface { type baseCache struct { clock Clock size int + shardCount int loaderExpireFunc LoaderExpireFunc evictedFunc EvictedFunc purgeVisitorFunc PurgeVisitorFunc @@ -77,6 +78,7 @@ type CacheBuilder struct { clock Clock tp string size int + shardCount int loaderExpireFunc LoaderExpireFunc evictedFunc EvictedFunc purgeVisitorFunc PurgeVisitorFunc @@ -173,6 +175,15 @@ func (cb *CacheBuilder) Build() Cache { panic("gcache: Cache size <= 0") } + if cb.shardCount <= 0 { + panic("gcache: Shard Count <= 0") + } + + return cb.build() +} + +func (cb *CacheBuilder) ShardCount(cnt int) Cache { + cb.shardCount = cnt return cb.build() } @@ -185,6 +196,9 @@ func (cb *CacheBuilder) build() Cache { case TYPE_LFU: return newLFUCache(cb) case TYPE_ARC: + if cb.shardCount > 0 { + return newARCShard(cb) + } return newARC(cb) default: panic("gcache: Unknown type " + cb.tp) @@ -194,6 +208,7 @@ func (cb *CacheBuilder) build() Cache { func buildCache(c *baseCache, cb *CacheBuilder) { c.clock = cb.clock c.size = cb.size + c.shardCount = cb.shardCount c.loaderExpireFunc = cb.loaderExpireFunc c.expiration = cb.expiration c.addedFunc = cb.addedFunc @@ -219,3 +234,15 @@ func (c *baseCache) load(key interface{}, cb func(interface{}, *time.Duration, e } return v, called, nil } + +func fnv32(k interface{}) uint32 { + key := k.(string) + hash := uint32(2166136261) + const prime32 = uint32(16777619) + keyLength := len(key) + for i := 0; i < keyLength; i++ { + hash *= prime32 + hash ^= uint32(key[i]) + } + return hash +} From 565bd8b0b5f451a967953f053db1bc7e9d7e95d8 Mon Sep 17 00:00:00 2001 From: zhanglei25 Date: Sat, 18 Jun 2022 16:41:08 +0800 Subject: [PATCH 2/4] add test for shard_arc --- arc_shard.go | 17 +++++-- arc_shard_test.go | 119 ++++++++++++++++++++++++++++++++++++++++++++++ cache.go | 4 +- helpers_test.go | 14 ++++++ 4 files changed, 148 insertions(+), 6 deletions(-) create mode 100644 arc_shard_test.go diff --git a/arc_shard.go b/arc_shard.go index f8b6bce..4b4cbc2 100644 --- a/arc_shard.go +++ b/arc_shard.go @@ -87,11 +87,20 @@ func (c *ARCShard) Keys(checkExpired bool) []interface{} { } func (c *ARCShard) Len(checkExpired bool) int { - //TODO - return 0 + len := 0 + for _, arc := range c.shard { + l := arc.Len(checkExpired) + len += l + } + return len } func (c *ARCShard) Has(key interface{}) bool { - //TODO - return false + ok := false + for _, arc := range c.shard { + if ok = arc.Has(key); ok == true { + break + } + } + return ok } diff --git a/arc_shard_test.go b/arc_shard_test.go new file mode 100644 index 0000000..0d0e4bc --- /dev/null +++ b/arc_shard_test.go @@ -0,0 +1,119 @@ +package gcache + +import ( + "fmt" + "testing" + "time" +) + +func TestARCShardGet(t *testing.T) { + size := 10 + gc := buildTest(t, TYPE_ARC, size).ShardCount(3).Build() + //gc.Set("k1", "v1") + + testSetCache(t, gc, size) + testGetCache(t, gc, size) +} + +//func TestLoadingARCShardGet(t *testing.T) { +// size := 1000 +// numbers := 1000 +// testGetCache(t, buildTestLoadingCache(t, TYPE_ARC, size, loader), numbers) +//} + +func TestARCShardLength(t *testing.T) { + shardCount := 3 + gc := buildTestLoadingWithExpiration(t, TYPE_ARC, 2, time.Millisecond).ShardCount(shardCount).Build() + gc.Get("test1") + gc.Get("test2") + gc.Get("test3") + length := gc.Len(true) + expectedLength := 2 + if length < shardCount || length > shardCount*expectedLength { + t.Errorf("Expected length is %v, not %v", expectedLength, length) + } + time.Sleep(time.Millisecond) + gc.Get("test4") + length = gc.Len(true) + expectedLength = 1 + if length != expectedLength { + t.Errorf("Expected length is %v, not %v", expectedLength, length) + } +} + +func TestARCShardEvictItem(t *testing.T) { + cacheSize := 10 + numbers := cacheSize + 1 + gc := buildTestLoadingCache(t, TYPE_ARC, cacheSize, loader) + + for i := 0; i < numbers; i++ { + _, err := gc.Get(fmt.Sprintf("Key-%d", i)) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + } +} + +func TestARCShardPurgeCache(t *testing.T) { + cacheSize := 10 + purgeCount := 0 + gc := New(cacheSize). + ARC(). + LoaderFunc(loader). + PurgeVisitorFunc(func(k, v interface{}) { + purgeCount++ + }). + ShardCount(3). + Build() + + for i := 0; i < cacheSize; i++ { + _, err := gc.Get(fmt.Sprintf("Key-%d", i)) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + } + + gc.Purge() + + if purgeCount != cacheSize { + t.Errorf("failed to purge everything") + } +} + +// +//func TestARCShardGetIFPresent(t *testing.T) { +// testGetIFPresent(t, TYPE_ARC) +//} +// +func TestARCShardHas(t *testing.T) { + gc := buildTestLoadingWithExpiration(t, TYPE_ARC, 2, 10*time.Millisecond).ShardCount(3).Build() + + for i := 0; i < 10; i++ { + t.Run(fmt.Sprint(i), func(t *testing.T) { + gc.Get("test1") + gc.Get("test2") + + if gc.Has("test0") { + t.Fatal("should not have test0") + } + if !gc.Has("test1") { + t.Fatal("should have test1") + } + if !gc.Has("test2") { + t.Fatal("should have test2") + } + + time.Sleep(20 * time.Millisecond) + + if gc.Has("test0") { + t.Fatal("should not have test0") + } + if gc.Has("test1") { + t.Fatal("should not have test1") + } + if gc.Has("test2") { + t.Fatal("should not have test2") + } + }) + } +} diff --git a/cache.go b/cache.go index 497b433..bdb7d05 100644 --- a/cache.go +++ b/cache.go @@ -182,9 +182,9 @@ func (cb *CacheBuilder) Build() Cache { return cb.build() } -func (cb *CacheBuilder) ShardCount(cnt int) Cache { +func (cb *CacheBuilder) ShardCount(cnt int) *CacheBuilder { cb.shardCount = cnt - return cb.build() + return cb } func (cb *CacheBuilder) build() Cache { diff --git a/helpers_test.go b/helpers_test.go index e254acc..5209a88 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -161,3 +161,17 @@ func buildTestLoadingCacheWithExpiration(t *testing.T, tp string, size int, ep t EvictedFunc(getSimpleEvictedFunc(t)). Build() } + +func buildTest(t *testing.T, tp string, size int) *CacheBuilder { + return New(size). + EvictType(tp). + EvictedFunc(getSimpleEvictedFunc(t)) +} + +func buildTestLoadingWithExpiration(t *testing.T, tp string, size int, ep time.Duration) *CacheBuilder { + return New(size). + EvictType(tp). + Expiration(ep). + LoaderFunc(loader). + EvictedFunc(getSimpleEvictedFunc(t)) +} From c2ffe7b99dab9937da3b6265147e89a03643ac4a Mon Sep 17 00:00:00 2001 From: zhanglei25 Date: Wed, 22 Jun 2022 11:23:43 +0800 Subject: [PATCH 3/4] update Has function for arc_shard --- arc_shard.go | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/arc_shard.go b/arc_shard.go index 4b4cbc2..a0e6094 100644 --- a/arc_shard.go +++ b/arc_shard.go @@ -1,6 +1,8 @@ package gcache -import "time" +import ( + "time" +) type ARCShard struct { shard []*ARC @@ -96,11 +98,6 @@ func (c *ARCShard) Len(checkExpired bool) int { } func (c *ARCShard) Has(key interface{}) bool { - ok := false - for _, arc := range c.shard { - if ok = arc.Has(key); ok == true { - break - } - } - return ok + arc := c.getShard(key) + return arc.Has(key) } From 50b50e486f9924504ae9fcb86be9da1bacc43312 Mon Sep 17 00:00:00 2001 From: zhanglei25 Date: Wed, 22 Jun 2022 14:17:56 +0800 Subject: [PATCH 4/4] add linkname Hash function --- arc_shard.go | 7 ++++++- arc_shard_test.go | 6 ++++++ cache.go | 12 ------------ 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/arc_shard.go b/arc_shard.go index a0e6094..1fa7841 100644 --- a/arc_shard.go +++ b/arc_shard.go @@ -2,6 +2,7 @@ package gcache import ( "time" + _ "unsafe" ) type ARCShard struct { @@ -31,7 +32,8 @@ func (c *ARCShard) init(cb *CacheBuilder) { // GetShard returns shard under given key func (c *ARCShard) getShard(key interface{}) *ARC { - return c.shard[uint(fnv32(key))%uint(c.shardCount)] + hash := efaceHash(key, 1) + return c.shard[uint(hash)%uint(c.shardCount)] } func (c *ARCShard) Set(key, value interface{}) error { @@ -101,3 +103,6 @@ func (c *ARCShard) Has(key interface{}) bool { arc := c.getShard(key) return arc.Has(key) } + +//go:linkname efaceHash runtime.efaceHash +func efaceHash(i interface{}, seed uintptr) uintptr diff --git a/arc_shard_test.go b/arc_shard_test.go index 0d0e4bc..292afda 100644 --- a/arc_shard_test.go +++ b/arc_shard_test.go @@ -117,3 +117,9 @@ func TestARCShardHas(t *testing.T) { }) } } + +func TestKey(t *testing.T) { + key := "bbb" + h := efaceHash(key, 1) + t.Log(h) +} diff --git a/cache.go b/cache.go index bdb7d05..099989c 100644 --- a/cache.go +++ b/cache.go @@ -234,15 +234,3 @@ func (c *baseCache) load(key interface{}, cb func(interface{}, *time.Duration, e } return v, called, nil } - -func fnv32(k interface{}) uint32 { - key := k.(string) - hash := uint32(2166136261) - const prime32 = uint32(16777619) - keyLength := len(key) - for i := 0; i < keyLength; i++ { - hash *= prime32 - hash ^= uint32(key[i]) - } - return hash -}