Skip to content

Commit

Permalink
opt: refact dict with swissmap
Browse files Browse the repository at this point in the history
  • Loading branch information
xgzlucario committed Jul 3, 2024
1 parent ae15977 commit 4da93d0
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 7 deletions.
74 changes: 74 additions & 0 deletions internal/dict/bench_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package dict

import (
"runtime"
"testing"

"github.com/cockroachdb/swiss"
)

const N = 10000

func BenchmarkSet(b *testing.B) {
b.Run("stdmap", func(b *testing.B) {
m := make(map[string]any, 8)
for i := 0; i < b.N; i++ {
k, v := genKV(i)
m[k] = v
}
})
b.Run("swiss", func(b *testing.B) {
m := swiss.New[string, any](8)
for i := 0; i < b.N; i++ {
k, v := genKV(i)
m.Put(k, v)
}
})
b.Run("swiss-shard", func(b *testing.B) {
const shardCount = 1024
m := make([]*swiss.Map[string, any], shardCount)
for i := range m {
m[i] = swiss.New[string, any](8)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
k, v := genKV(i)
m[i%shardCount].Put(k, v)
}
})
}

func BenchmarkGC(b *testing.B) {
b.Run("swiss", func(b *testing.B) {
m := swiss.New[string, any](N)
for i := 0; i < N; i++ {
k, v := genKV(i)
m.Put(k, v)
}

b.ResetTimer()
for i := 0; i < b.N; i++ {
runtime.GC()
}

m.Put("xgz", []byte("hello"))
})
b.Run("swiss-shard", func(b *testing.B) {
const shardCount = 1024
m := make([]*swiss.Map[string, any], shardCount)
for i := range m {
m[i] = swiss.New[string, any](8)
}
for i := 0; i < N; i++ {
k, v := genKV(i)
m[i%shardCount].Put(k, v)
}

b.ResetTimer()
for i := 0; i < b.N; i++ {
runtime.GC()
}

m[0].Put("xgz", []byte("hello"))
})
}
101 changes: 101 additions & 0 deletions internal/dict/benchmark/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package main

import (
"flag"
"fmt"
"runtime"
"runtime/debug"
"time"

"github.com/cockroachdb/swiss"
"github.com/xgzlucario/rotom/internal/dict"
"github.com/xgzlucario/rotom/internal/pkg"
)

var previousPause time.Duration

func gcPause() time.Duration {
runtime.GC()
var stats debug.GCStats
debug.ReadGCStats(&stats)
pause := stats.PauseTotal - previousPause
previousPause = stats.PauseTotal
return pause
}

func genKV(id int) (string, []byte) {
k := fmt.Sprintf("%08x", id)
return k, []byte(k)
}

func main() {
c := ""
entries := 0
flag.StringVar(&c, "cache", "dict", "map to bench.")
flag.IntVar(&entries, "entries", 2000*10000, "number of entries to test.")
flag.Parse()

fmt.Println(c)
fmt.Println("entries:", entries)

debug.SetGCPercent(10)
start := time.Now()
q := pkg.NewQuantile(entries)

switch c {
case "dict":
m := dict.New()
for i := 0; i < entries; i++ {
k, v := genKV(i)
start := time.Now()
m.Set(k, v)
q.Add(float64(time.Since(start)))
}

case "stdmap":
m := make(map[string]any, 8)
for i := 0; i < entries; i++ {
k, v := genKV(i)
start := time.Now()
m[k] = v
q.Add(float64(time.Since(start)))
}

case "swiss":
m := swiss.New[string, any](8)
for i := 0; i < entries; i++ {
k, v := genKV(i)
start := time.Now()
m.Put(k, v)
q.Add(float64(time.Since(start)))
}

case "swiss-shard":
const shardCount = 1024
m := make([]*swiss.Map[string, any], shardCount)
for i := range m {
m[i] = swiss.New[string, any](8)
}
for i := 0; i < entries; i++ {
k, v := genKV(i)
start := time.Now()
m[i%shardCount].Put(k, v)
q.Add(float64(time.Since(start)))
}
}
cost := time.Since(start)

var mem runtime.MemStats
var stat debug.GCStats

runtime.ReadMemStats(&mem)
debug.ReadGCStats(&stat)

fmt.Println("gcsys:", mem.GCSys/1024/1024, "mb")
fmt.Println("heap inuse:", mem.HeapInuse/1024/1024, "mb")
fmt.Println("heap object:", mem.HeapObjects/1024, "k")
fmt.Println("gc:", stat.NumGC)
fmt.Println("pause:", gcPause())
fmt.Println("cost:", cost)
q.Print()
}
6 changes: 2 additions & 4 deletions internal/dict/dict.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,8 @@ func (dict *Dict) Get(key string) (any, bool) {
return dict.data.Get(key)
}

func (dict *Dict) Set(key string, value any) bool {
_, ok := dict.data.Get(key)
func (dict *Dict) Set(key string, value any) {
dict.data.Put(key, value)
return !ok
}

func (dict *Dict) Remove(key string) bool {
Expand All @@ -45,7 +43,7 @@ func (dict *Dict) SetTTL(key string, expiration int64) bool {
return true
}

func (dict *Dict) EvictExpired(key string, expiration int64) {
func (dict *Dict) EvictExpired() {
nanosec := time.Now().UnixNano()
count := 0
dict.expire.All(func(key string, value int64) bool {
Expand Down
2 changes: 1 addition & 1 deletion internal/dict/dict_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
)

func genKV(i int) (string, []byte) {
k := fmt.Sprintf("%09x", i)
k := fmt.Sprintf("%08x", i)
return k, []byte(k)
}

Expand Down
24 changes: 24 additions & 0 deletions internal/dict/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package dict

import (
"unsafe"
)

type stringStruct struct {
str unsafe.Pointer
len int
}

//go:noescape
//go:linkname memhash runtime.memhash
func memhash(p unsafe.Pointer, h, s uintptr) uintptr

type HashFn func(string) uint64

// MemHash is the hash function used by go map, it utilizes available hardware instructions
// (behaves as aes hash if aes instruction is available).
// NOTE: The hash seed changes for every process. So, this cannot be used as a persistent hash.
func MemHash(str string) uint64 {
ss := (*stringStruct)(unsafe.Pointer(&str))
return uint64(memhash(ss.str, 0, uintptr(ss.len)))
}
2 changes: 1 addition & 1 deletion internal/hash/set.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ type Set struct {
}

func NewSet() *Set {
return &Set{m: swiss.New[string, struct{}](8, swiss.WithAllocator(setAllocator))}
return &Set{m: swiss.New(8, swiss.WithAllocator(setAllocator))}
}

func (s *Set) Add(key string) bool {
Expand Down
2 changes: 1 addition & 1 deletion rotom.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,5 +207,5 @@ func ServerCronFlush(loop *AeLoop, id int, extra interface{}) {
}

func ServerCronEvict(loop *AeLoop, id int, extra interface{}) {
// db.strs.EvictExpired()
db.dict.EvictExpired()
}

0 comments on commit 4da93d0

Please sign in to comment.