diff --git a/arc_shard.go b/arc_shard.go new file mode 100644 index 0000000..1fa7841 --- /dev/null +++ b/arc_shard.go @@ -0,0 +1,108 @@ +package gcache + +import ( + "time" + _ "unsafe" +) + +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 { + hash := efaceHash(key, 1) + return c.shard[uint(hash)%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 { + len := 0 + for _, arc := range c.shard { + l := arc.Len(checkExpired) + len += l + } + return len +} + +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 new file mode 100644 index 0000000..292afda --- /dev/null +++ b/arc_shard_test.go @@ -0,0 +1,125 @@ +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") + } + }) + } +} + +func TestKey(t *testing.T) { + key := "bbb" + h := efaceHash(key, 1) + t.Log(h) +} diff --git a/cache.go b/cache.go index 0ae51bf..099989c 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,9 +175,18 @@ 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) *CacheBuilder { + cb.shardCount = cnt + return cb +} + func (cb *CacheBuilder) build() Cache { switch cb.tp { case TYPE_SIMPLE: @@ -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 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)) +}