Skip to content

Commit

Permalink
[+] feat: Add functions
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrew-M-C committed Nov 15, 2019
1 parent 0e2b011 commit 395c321
Show file tree
Hide file tree
Showing 4 changed files with 297 additions and 0 deletions.
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/Andrew-M-C/go.hotspotcache

go 1.13
127 changes: 127 additions & 0 deletions hotsoptcache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package hotspotcache

import (
"bytes"
"container/list"
"fmt"
"runtime"
"sync"
)

// Cache is the hotspot cache object
type Cache struct {
c *cache
}

type cache struct {
maxSize int
values sync.Map
agingList *list.List // one in the front is the hottest one
agingMap map[interface{}]*list.Element
access chan interface{}
stop chan bool
}

// New returns a initialized hotspot cache
func New(maxSize int) *Cache {
if maxSize <= 0 {
maxSize = 10240
}

c := newCache(maxSize)
ret := Cache{c: c}

go c.run()
runtime.SetFinalizer(&ret, stopRunning)

return &ret
}

// Load read a value in the cache. If value does not exist in cache, the return exist will be false, otherwise true. If value exists, its hotspot will also be set to the top.
func (c *Cache) Load(key interface{}) (value interface{}, exist bool) {
value, exist = c.c.values.Load(key)
if exist {
// log.Printf("Key %v not exists\n", key)
c.c.access <- key
}
// log.Printf("Key %v exists\n", key)
return
}

// Store saves a value in corresponding key.
func (c *Cache) Store(key, value interface{}) {
c.c.values.Store(key, value)
c.c.access <- key
return
}

// MaxSize returns the max size of this cache
func (c *Cache) MaxSize() int {
return c.c.maxSize
}

func newCache(maxSize int) *cache {
c := cache{
maxSize: maxSize,
agingList: list.New(),
agingMap: make(map[interface{}]*list.Element),
access: make(chan interface{}),
stop: make(chan bool),
}
return &c
}

func (c *Cache) dumpStatus() string {
buff := bytes.Buffer{}
buff.WriteString(fmt.Sprintf("\nMax size: %d", c.c.maxSize))
buff.WriteString(fmt.Sprintf("\naging list length: %d", c.c.agingList.Len()))
buff.WriteString(fmt.Sprintf("\nremain in channel: %d", len(c.c.access)))
return buff.String()
}

func (c *cache) run() {
for {
select {
case k := <-c.access:
c.updateHotspot(k)
case <-c.stop:
return
}
}
}

func (c *cache) updateHotspot(k interface{}) {
li := c.agingList
e, exist := c.agingMap[k]

// this key already in hot spotqueue
if exist {
// just move it to the front
// log.Printf("upd hotspot %v\n", k)
li.MoveToFront(e)
return
}

// this key not exists in hotspot queue
// log.Printf("new hotspot %v\n", k)
e = li.PushFront(k)
c.agingMap[k] = e

// check if the queue is full
if li.Len() <= c.maxSize {
return
}

// remove the coldest one
e = li.Back()
k = e.Value
c.values.Delete(k)
li.Remove(e)
delete(c.agingMap, k)
// log.Printf("del hotspot %v\n", k)
return
}

func stopRunning(c *Cache) {
c.c.stop <- true
}
83 changes: 83 additions & 0 deletions hotsoptcache_benchmark_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package hotspotcache

import (
"log"
"runtime"
"sync"
"testing"
)

func init() {
log.Printf("runtime.GOMAXPROCS(0) = %d\n", runtime.GOMAXPROCS(0))
}

func BenchmarkMultiRoutine(b *testing.B) {
c := New(b.N / 4)
mask := 1
for {
mask <<= 1
if mask >= b.N/8 {
break
}
}

MAX := runtime.GOMAXPROCS(0) * 2
var wg sync.WaitGroup
wg.Add(MAX)

for i := 0; i < MAX; i++ {
go func() {
defer wg.Done()
for i := 0; i < b.N; i++ {
n := i & mask
_, exist := c.Load(n)
if false == exist {
c.Store(n, i)
}
}
}()
}

wg.Wait()
b.Logf("b.N = %d", b.N)
return
}

func Benchmark75PercentWrite(b *testing.B) {
c := New(b.N / 4)

for i := 0; i < b.N; i++ {
n := i
_, exist := c.Load(n)
if false == exist {
c.Store(n, i)
}
}

b.Logf("b.N = %d", b.N)
return
}

func BenchmarkNoHitNoWrite(b *testing.B) {
c := New(b.N / 4)

for i := 0; i < b.N; i++ {
n := i
_, _ = c.Load(n)
}

b.Logf("b.N = %d", b.N)
return
}

func BenchmarkAllHit(b *testing.B) {
c := New(b.N / 4)
c.Store(1, 1)

for i := 0; i < b.N; i++ {
_, _ = c.Load(1)
}

b.Logf("b.N = %d", b.N)
return
}
84 changes: 84 additions & 0 deletions hotsoptcache_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package hotspotcache

import (
"math/rand"
"runtime"
"sync"
"testing"
"time"
)

func init() {
rand.Seed(time.Now().UnixNano())
}

func TestBasic(t *testing.T) {
const N = 20000
const SIZE = 0 // causes default as 10240
c := New(SIZE)

for i := 0; i < SIZE; i++ {
c.Store(i, i)
}

time.Sleep(time.Second)

for i := 0; i < SIZE; i++ {
res, exist := c.Load(i)
if false == exist {
t.Errorf("Key %v not exist", i)
return
}
if res.(int) != i {
t.Errorf("Key %v not equal to %v", res, i)
}
}

nonexistCount := 0

for i := 0; i < N; i++ {
n := int(rand.Int31n(N))
_, exist := c.Load(n)
if false == exist {
nonexistCount++
c.Store(n, n)
}
}

t.Logf("max size: %d", c.MaxSize())
t.Logf("nonexist percentage: %.02f%%", float64(nonexistCount)/float64(N)*100)

return
}

func TestConcurrency(t *testing.T) {
const N = 1000000
const SIZE = 50000
c := New(SIZE)

MAX := runtime.GOMAXPROCS(0) * 2
var wg sync.WaitGroup
wg.Add(MAX)

for i := 0; i < MAX; i++ {
go func() {
defer wg.Done()
for i := 0; i < N; i++ {
n := int(rand.Int31n(N))
_, exist := c.Load(n)
if false == exist {
c.Store(n, n)
}
}
}()
}

time.Sleep(10 * time.Millisecond)
t.Logf("inter status: %s", c.dumpStatus())

wg.Wait()
t.Logf("status: %s", c.dumpStatus())
time.Sleep(time.Second)
t.Logf("status: %s", c.dumpStatus())
return
}

0 comments on commit 395c321

Please sign in to comment.