diff --git a/container/strmap/strmap.go b/container/strmap/strmap.go index 14a9963..7ec1942 100644 --- a/container/strmap/strmap.go +++ b/container/strmap/strmap.go @@ -25,6 +25,7 @@ import ( "github.com/cloudwego/gopkg/internal/hack" "github.com/cloudwego/gopkg/internal/hash/maphash" + "github.com/cloudwego/gopkg/internal/strstore" ) // StrMap represents GC friendly readonly string map implementation. @@ -219,3 +220,60 @@ func (m *StrMap[V]) debugString() string { fmt.Fprintf(b, "}(slots=%d, items=%d)", len(m.hashtable), len(m.items)) return b.String() } + +// Str2Str uses StrMap and strstore.StrStore to store map[string]string +type Str2Str struct { + strMap *StrMap[int] + strStore *strstore.StrStore +} + +func NewStr2Str() *Str2Str { + return &Str2Str{} +} + +// NewStr2StrFromSlice creates StrMapStr2Str from key, value slices. +func NewStr2StrFromSlice(kk, vv []string) *Str2Str { + m := NewStr2Str() + m.LoadFromSlice(kk, vv) + return m +} + +// NewStr2StrFromMap creates StrMapStr2Str from map. +func NewStr2StrFromMap(m map[string]string) *Str2Str { + sm := NewStr2Str() + sm.LoadFromMap(m) + return sm +} + +// LoadFromSlice resets Str2Str and loads from slices. +func (sm *Str2Str) LoadFromSlice(kk, vv []string) { + ss, ids := strstore.New(vv) + sm.strStore = ss + sm.strMap = NewFromSlice(kk, ids) +} + +// LoadFromMap resets Str2Str and loads from map. +func (sm *Str2Str) LoadFromMap(m map[string]string) { + kk := make([]string, 0, len(m)) + vv := make([]string, 0, len(m)) + for k, v := range m { + kk = append(kk, k) + vv = append(vv, v) + } + sm.LoadFromSlice(kk, vv) +} + +// Get ... +func (sm *Str2Str) Get(k string) (string, bool) { + if idx, ok := sm.strMap.Get(k); ok { + v := sm.strStore.Get(idx) + // TODO: any check? + return v, true + } + return "", false +} + +// Len returns the size of map +func (sm *Str2Str) Len() int { + return sm.strMap.Len() +} diff --git a/container/strmap/strmap_test.go b/container/strmap/strmap_test.go index ca4bdf1..495338a 100644 --- a/container/strmap/strmap_test.go +++ b/container/strmap/strmap_test.go @@ -52,6 +52,18 @@ func newStdStrMap(ss []string) map[string]uint { return m } +// newStdStr2StrMap generates a map with uniq values +func newStdStr2StrMap(kk, vv []string) map[string]string { + if len(kk) != len(vv) { + panic("len(kk) != len(vv)") + } + m := make(map[string]string, len(kk)) + for i := 0; i < len(kk); i++ { + m[kk[i]] = vv[i] + } + return m +} + func TestStrMap(t *testing.T) { ss := randStrings(20, 100000) m := newStdStrMap(ss) @@ -84,6 +96,30 @@ func TestStrMapString(t *testing.T) { t.Log(sm.debugString()) } +func TestStrMapStr(t *testing.T) { + kk := randStrings(20, 100000) + vv := randStrings(20, 100000) + m := newStdStr2StrMap(kk, vv) + + // from slice + ms := NewStr2StrFromSlice(kk, vv) + require.Equal(t, len(m), ms.Len()) + for i, k := range kk { + v0 := vv[i] + v1, _ := ms.Get(k) + require.Equal(t, v0, v1, i) + } + + // from map + mm := NewStr2StrFromMap(m) + require.Equal(t, len(m), mm.Len()) + for i, k := range kk { + v0 := vv[i] + v1, _ := mm.Get(k) + require.Equal(t, v0, v1, i) + } +} + func BenchmarkLoadFromMap(b *testing.B) { sz := 50 n := 100000 @@ -165,3 +201,36 @@ func BenchmarkGC(b *testing.B) { } } } + +func BenchmarkStr2StrMapGC(b *testing.B) { + sizes := []int{20, 100} + nn := []int{100000, 400000} + + for _, n := range nn { + for _, sz := range sizes { + kk := randStrings(sz, n) + vv := randStrings(sz, n) + m := newStdStr2StrMap(kk, vv) + + b.Run(fmt.Sprintf("std-keysize_%d_n_%d", sz, n), func(b *testing.B) { + for i := 0; i < b.N; i++ { + runtime.GC() + } + }) + + sm := NewStr2StrFromMap(m) + m = nil + runtime.GC() + + b.Run(fmt.Sprintf("new-keysize_%d_n_%d", sz, n), func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + runtime.GC() + } + }) + + _ = m // fix lint ineffassign of m = nil + runtime.KeepAlive(sm) + } + } +} diff --git a/container/strstore/strstore.go b/internal/strstore/strstore.go similarity index 100% rename from container/strstore/strstore.go rename to internal/strstore/strstore.go diff --git a/container/strstore/strstore_test.go b/internal/strstore/strstore_test.go similarity index 100% rename from container/strstore/strstore_test.go rename to internal/strstore/strstore_test.go