Skip to content

Commit

Permalink
perf: metainfo reduce mallocgc
Browse files Browse the repository at this point in the history
  • Loading branch information
joway committed Jan 10, 2024
1 parent a5eedbe commit a12ac76
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 30 deletions.
4 changes: 3 additions & 1 deletion cloud/metainfo/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,9 @@ func FromHTTPHeader(ctx context.Context, h HTTPHeaderCarrier) context.Context {
// TODO: remove this?
return ctx
}
return withNode(ctx, m.toNode())
ctx = withNode(ctx, m.toNode())
m.recycle()
return ctx
}

// ToHTTPHeader writes all metainfo into the given HTTP header.
Expand Down
42 changes: 29 additions & 13 deletions cloud/metainfo/kv.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@

package metainfo

import "context"
import (
"context"
"sync"
)

type ctxKeyType struct{}

Expand Down Expand Up @@ -122,18 +125,31 @@ func (n *node) delPersistent(k string) (r *node) {
return n
}

var mapViewPool sync.Pool

type mapView struct {
persistent map[string]string
transient map[string]string
stale map[string]string
persistent kvstore
transient kvstore
stale kvstore
}

func newMapView() *mapView {
return &mapView{
persistent: make(map[string]string),
transient: make(map[string]string),
stale: make(map[string]string),
mv := mapViewPool.Get()
if mv == nil {
return &mapView{
persistent: newKVStore(),
transient: newKVStore(),
stale: newKVStore(),
}
}
return mv.(*mapView)
}

func (m *mapView) recycle() {
m.persistent.recycle()
m.transient.recycle()
m.stale.recycle()
mapViewPool.Put(m)
}

func (m *mapView) size() int {
Expand All @@ -149,11 +165,11 @@ func (m *mapView) toNode() *node {
}

func (n *node) mapView() *mapView {
return &mapView{
persistent: sliceToMap(n.persistent),
transient: sliceToMap(n.transient),
stale: sliceToMap(n.stale),
}
mv := newMapView()
sliceToMap(n.persistent, mv.persistent)
sliceToMap(n.transient, mv.transient)
sliceToMap(n.stale, mv.stale)
return mv
}

func search(kvs []kv, key string) (idx int, ok bool) {
Expand Down
51 changes: 51 additions & 0 deletions cloud/metainfo/kvstore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright 2023 ByteDance Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package metainfo

import "sync"

type kvstore map[string]string

var kvpool sync.Pool

func newKVStore(size ...int) kvstore {
kvs := kvpool.Get()
if kvs == nil {
if len(size) > 0 {
return make(kvstore, size[0])
}
return make(kvstore)
}
return kvs.(kvstore)
}

func (store kvstore) recycle() {
/*
for k := range m {
delete(m, k)
}
==>
LEAQ type.map[string]int(SB), AX
MOVQ AX, (SP)
MOVQ "".m(SB), AX
MOVQ AX, 8(SP)
PCDATA $1, $0
CALL runtime.mapclear(SB)
*/
for key := range store {
delete(store, key)
}
kvpool.Put(store)
}
66 changes: 66 additions & 0 deletions cloud/metainfo/kvstore_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright 2023 ByteDance Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package metainfo

import (
"fmt"
"testing"
)

func TestKVStore(t *testing.T) {
store := newKVStore()
store["a"] = "a"
store["a"] = "b"
if store["a"] != "b" {
t.Fatal()
}
store.recycle()
if store["a"] == "b" {
t.Fatal()
}
store = newKVStore()
if store["a"] == "b" {
t.Fatal()
}
}

func BenchmarkMap(b *testing.B) {
for keys := 1; keys <= 1000; keys *= 10 {
b.Run(fmt.Sprintf("keys=%d", keys), func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
m := make(map[string]string)
for idx := 0; idx < 1000; idx++ {
m[fmt.Sprintf("key-%d", idx)] = string('a' + byte(idx%26))
}
}
})
}
}

func BenchmarkKVStore(b *testing.B) {
for keys := 1; keys <= 1000; keys *= 10 {
b.Run(fmt.Sprintf("keys=%d", keys), func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
m := newKVStore()
for idx := 0; idx < 1000; idx++ {
m[fmt.Sprintf("key-%d", idx)] = string('a' + byte(idx%26))
}
m.recycle()
}
})
}
}
33 changes: 17 additions & 16 deletions cloud/metainfo/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,9 @@ func SetMetaInfoFromMap(ctx context.Context, m map[string]string) context.Contex
return ctx
}

return withNode(ctx, mv.toNode())
ctx = withNode(ctx, mv.toNode())
mv.recycle()
return ctx
}

// SaveMetaInfoToMap set key-value pairs from ctx to m while filtering out transient-upstream data.
Expand All @@ -78,29 +80,28 @@ func SaveMetaInfoToMap(ctx context.Context, m map[string]string) {
return
}
ctx = TransferForward(ctx)
for k, v := range GetAllValues(ctx) {
m[PrefixTransient+k] = v
}
for k, v := range GetAllPersistentValues(ctx) {
m[PrefixPersistent+k] = v
if n := getNode(ctx); n != nil {
for _, kv := range n.persistent {
m[PrefixPersistent+kv.key] = kv.val
}
for _, kv := range n.transient {
m[PrefixTransient+kv.key] = kv.val
}
for _, kv := range n.stale {
m[PrefixTransient+kv.key] = kv.val
}
}
}

// sliceToMap converts a kv slice to map. If the slice is empty, an empty map will be returned instead of nil.
func sliceToMap(slice []kv) (m map[string]string) {
if size := len(slice); size == 0 {
m = make(map[string]string)
} else {
m = make(map[string]string, size)
}
// sliceToMap converts a kv slice to map.
func sliceToMap(slice []kv, kvs kvstore) {
for _, kv := range slice {
m[kv.key] = kv.val
kvs[kv.key] = kv.val
}
return
}

// mapToSlice converts a map to a kv slice. If the map is empty, the return value will be nil.
func mapToSlice(kvs map[string]string) (slice []kv) {
func mapToSlice(kvs kvstore) (slice []kv) {
size := len(kvs)
if size == 0 {
return
Expand Down
14 changes: 14 additions & 0 deletions cloud/metainfo/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package metainfo_test

import (
"context"
"fmt"
"testing"

"github.com/bytedance/gopkg/cloud/metainfo"
Expand Down Expand Up @@ -146,3 +147,16 @@ func TestSaveMetaInfoToMap(t *testing.T) {
assert(t, m[metainfo.PrefixTransient+"b"] == "b2")
assert(t, m[metainfo.PrefixPersistent+"b"] == "b3")
}

func BenchmarkSetMetaInfoFromMap(b *testing.B) {
ctx := metainfo.WithPersistentValue(context.Background(), "key", "val")
m := map[string]string{}
for i := 0; i < 32; i++ {
m[fmt.Sprintf("key-%d", i)] = fmt.Sprintf("val-%d", i)
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = metainfo.SetMetaInfoFromMap(ctx, m)
}
}

0 comments on commit a12ac76

Please sign in to comment.