From 0b613791b8fdd04995cad42619fe46f4a8a25841 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quang=20T=C3=B9ng?= Date: Fri, 6 Oct 2023 14:00:40 +0700 Subject: [PATCH] Add Benchmark MMap (#22) --- mmap/bucket.go | 8 +- mmap/mmap.go | 7 +- mmap/mmap_bench_test.go | 169 +++++++++++++++++++++++++++++++++++++ mmap/mmap_property_test.go | 6 +- 4 files changed, 187 insertions(+), 3 deletions(-) create mode 100644 mmap/mmap_bench_test.go diff --git a/mmap/bucket.go b/mmap/bucket.go index ae8c10c..922a917 100644 --- a/mmap/bucket.go +++ b/mmap/bucket.go @@ -22,9 +22,15 @@ type BucketKey[R RootKey] struct { // String ... func (k BucketKey[R]) String() string { + rootKey := k.RootKey.String() + var buf strings.Builder - _, _ = buf.WriteString(k.RootKey.String()) + // 2 byte for size log + // 8 byte for hash value => can contain 4 byte uint32 + buf.Grow(len(rootKey) + 2*len(k.Sep) + 10) + + _, _ = buf.WriteString(rootKey) _, _ = buf.WriteString(k.Sep) _, _ = buf.WriteString(strconv.FormatInt(int64(k.SizeLog), 10)) _, _ = buf.WriteString(k.Sep) diff --git a/mmap/mmap.go b/mmap/mmap.go index 5f25979..f4816c5 100644 --- a/mmap/mmap.go +++ b/mmap/mmap.go @@ -12,6 +12,11 @@ import ( // RootKey constraints type RootKey interface { item.Key + + // AvgBucketSizeLog returns the logarithm base 2 of expected average size per bucket + // values should be between [0, 8] + // value = 0 => average 1 element per bucket + // value = 3 => average 8 elements per bucket AvgBucketSizeLog() uint8 } @@ -80,7 +85,7 @@ func New[T Value, R RootKey, K Key]( } } -// Option ... +// Option an optional value type Option[T any] struct { Valid bool Data T diff --git a/mmap/mmap_bench_test.go b/mmap/mmap_bench_test.go new file mode 100644 index 0000000..dd87de4 --- /dev/null +++ b/mmap/mmap_bench_test.go @@ -0,0 +1,169 @@ +package mmap + +import ( + "context" + "encoding/binary" + "strconv" + "testing" + + "github.com/spaolacci/murmur3" + "github.com/stretchr/testify/assert" + + "github.com/QuangTung97/memproxy" +) + +type benchValue struct { + rootKey benchRootKey + key benchKey + value int64 +} + +type benchRootKey struct { + value uint64 +} + +type benchKey struct { + value uint64 +} + +const benchKeyNum = 229 +const benchValueNum = 331 + +func newBenchMapCache(pipe memproxy.Pipeline) *Map[benchValue, benchRootKey, benchKey] { + return New[benchValue, benchRootKey, benchKey]( + pipe, + unmarshalBenchValue, + func(ctx context.Context, rootKey benchRootKey, hashRange HashRange) func() ([]benchValue, error) { + return func() ([]benchValue, error) { + return []benchValue{ + { + rootKey: rootKey, + key: benchKey{ + value: benchKeyNum, + }, + value: benchValueNum, + }, + }, nil + } + }, + benchValue.getKey, + ) +} + +func doGetMapElemFromMemcache(mc memproxy.Memcache, numKeys int) { + pipe := mc.Pipeline(context.Background()) + defer pipe.Finish() + + mapCache := newBenchMapCache(pipe) + + fnList := make([]func() (Option[benchValue], error), 0, numKeys) + for i := 0; i < numKeys; i++ { + fn := mapCache.Get(context.Background(), uint64(numKeys), benchRootKey{ + value: uint64(1000 + i), + }, benchKey{ + value: benchKeyNum, + }) + fnList = append(fnList, fn) + } + + for _, fn := range fnList { + result, err := fn() + if err != nil { + panic(err) + } + if !result.Valid { + panic("not valid") + } + if result.Data.value != benchValueNum { + panic("wrong value") + } + } +} + +func Benchmark_Proxy__Map_Get_Batch_100(b *testing.B) { + mc := newMemcacheWithProxy(b) + + const numKeys = 100 + + doGetMapElemFromMemcache(mc, numKeys) + + b.ResetTimer() + + for n := 0; n < b.N; n++ { + doGetMapElemFromMemcache(mc, numKeys) + } +} + +func Benchmark_Proxy__Map_Get_Batch_1000(b *testing.B) { + mc := newMemcacheWithProxy(b) + + const numKeys = 1000 + + doGetMapElemFromMemcache(mc, numKeys) + + b.ResetTimer() + + for n := 0; n < b.N; n++ { + doGetMapElemFromMemcache(mc, numKeys) + } +} + +func (v benchValue) getKey() benchKey { + return v.key +} + +func (k benchKey) Hash() uint64 { + var data [8]byte + binary.LittleEndian.PutUint64(data[:], k.value) + return murmur3.Sum64(data[:]) +} + +func (k benchRootKey) String() string { + return strconv.FormatUint(k.value, 10) +} + +func (benchRootKey) AvgBucketSizeLog() uint8 { + return 1 +} + +func (v benchValue) Marshal() ([]byte, error) { + var result [24]byte + binary.LittleEndian.PutUint64(result[:], v.rootKey.value) + binary.LittleEndian.PutUint64(result[8:], v.key.value) + binary.LittleEndian.PutUint64(result[16:], uint64(v.value)) + return result[:], nil +} + +func unmarshalBenchValue(data []byte) (benchValue, error) { + rootKey := binary.LittleEndian.Uint64(data[:]) + key := binary.LittleEndian.Uint64(data[8:]) + val := binary.LittleEndian.Uint64(data[16:]) + + return benchValue{ + rootKey: benchRootKey{ + value: rootKey, + }, + key: benchKey{ + value: key, + }, + value: int64(val), + }, nil +} + +func TestMarshalBenchValue(t *testing.T) { + b := benchValue{ + rootKey: benchRootKey{ + value: 41, + }, + key: benchKey{ + value: 31, + }, + value: 55, + } + data, err := b.Marshal() + assert.Equal(t, nil, err) + + newVal, err := unmarshalBenchValue(data) + assert.Equal(t, nil, err) + assert.Equal(t, b, newVal) +} diff --git a/mmap/mmap_property_test.go b/mmap/mmap_property_test.go index ccc1c3f..3954145 100644 --- a/mmap/mmap_property_test.go +++ b/mmap/mmap_property_test.go @@ -189,7 +189,11 @@ func clearMemcache() { } } -func newMemcacheWithProxy(t *testing.T) memproxy.Memcache { +type cleanerInterface interface { + Cleanup(fn func()) +} + +func newMemcacheWithProxy(t cleanerInterface) memproxy.Memcache { clearMemcache() server1 := proxy.SimpleServerConfig{