Repository: tikv/pd (go)

Issue:
optimization: using mashal to replace proto clone 
## Development Task
Proto.Clone is too expensive:

```
// origin 
copyLeader := proto.Clone(leader).(*metapb.Peer)
// new
b, _ := leader.Marshal()
copyLeader := &metapb.Peer{}
copyLeader.Unmarshal(b)
```


before :
<img width="551" alt="image" src="https://user-images.githubusercontent.com/23159587/188845425-82a53362-7cbe-4442-9236-e979acc37ca0.png">

after
<img width="308" alt="image" src="https://user-images.githubusercontent.com/23159587/188845480-d20a296b-59bd-4d66-849a-be31e28c4cc6.png">


Pull request:
optimization: using mashal to replace proto clone 
Signed-off-by: bufferflies <1045931706@qq.com>

<!--
Thank you for working on PD! Please read PD's [CONTRIBUTING](https://github.com/tikv/pd/blob/master/CONTRIBUTING.md) document **BEFORE** filing this PR.
PR Title Format:
1. pkg [, pkg2, pkg3]: what's changed
2. *: what's changed
-->

### What problem does this PR solve?

<!--

Please create an issue first to describe the problem.
There MUST be one line starting with "Issue Number:  " and 
linking the relevant issues via the "close" or "ref".
For more info, check https://github.com/tikv/pd/blob/master/CONTRIBUTING.md#linking-issues.

-->
Issue Number: Close #5477

### What is changed and how does it work?

<!--

You could use the "commit message" code block to add more description to the final commit message.
For more info, check https://github.com/tikv/pd/blob/master/CONTRIBUTING.md#format-of-the-commit-message.

-->

```commit-message
```

### Check List

<!-- Remove the items that are not applicable. -->

Tests

<!-- At least one of these tests must be included. -->

- Unit test
- Integration test

benchmark
<img width="1002" alt="image" src="https://user-images.githubusercontent.com/23159587/190049483-8f2b18c9-55d2-49d9-ad03-8fd46d5cf248.png">




Code changes



Side effects

Related changes



### Release note

<!-- A bugfix or a new feature needs a release note. If there is no need release note, just uncomment the below line. -->

```release-note
 None.
```


Patch:
diff --git a/pkg/encryption/region_crypter.go b/pkg/encryption/region_crypter.go
index 3f85cd491..23931b625 100644
--- a/pkg/encryption/region_crypter.go
+++ b/pkg/encryption/region_crypter.go
@@ -19,11 +19,12 @@ import (
 	"crypto/cipher"
 	"reflect"
 
-	"github.com/gogo/protobuf/proto"
 	"github.com/pingcap/errors"
 	"github.com/pingcap/kvproto/pkg/encryptionpb"
 	"github.com/pingcap/kvproto/pkg/metapb"
 	"github.com/tikv/pd/pkg/errs"
+	"github.com/tikv/pd/pkg/typeutil"
+	"github.com/tikv/pd/server/core"
 )
 
 // processRegionKeys encrypt or decrypt the start key and end key of the region in-place,
@@ -71,7 +72,7 @@ func EncryptRegion(region *metapb.Region, keyManager KeyManager) (*metapb.Region
 		return nil, err
 	}
 	// Deep copy region before altering it.
-	outRegion := proto.Clone(region).(*metapb.Region)
+	outRegion := typeutil.DeepClone(region, core.RegionFactory)
 	// Encrypt and update in-place.
 	err = processRegionKeys(outRegion, key, iv)
 	if err != nil {
diff --git a/pkg/mock/mockcluster/mockcluster.go b/pkg/mock/mockcluster/mockcluster.go
index 122afa872..fffc2b02e 100644
--- a/pkg/mock/mockcluster/mockcluster.go
+++ b/pkg/mock/mockcluster/mockcluster.go
@@ -21,12 +21,12 @@ import (
 	"time"
 
 	"github.com/docker/go-units"
-	"github.com/gogo/protobuf/proto"
 	"github.com/pingcap/kvproto/pkg/metapb"
 	"github.com/pingcap/kvproto/pkg/pdpb"
 	"github.com/pingcap/log"
 	"github.com/tikv/pd/pkg/errs"
 	"github.com/tikv/pd/pkg/mock/mockid"
+	"github.com/tikv/pd/pkg/typeutil"
 	"github.com/tikv/pd/server/config"
 	"github.com/tikv/pd/server/core"
 	"github.com/tikv/pd/server/core/storelimit"
@@ -242,7 +242,7 @@ func (mc *Cluster) SetStoreOffline(storeID uint64) {
 // SetStoreBusy sets store busy.
 func (mc *Cluster) SetStoreBusy(storeID uint64, busy bool) {
 	store := mc.GetStore(storeID)
-	newStats := proto.Clone(store.GetStoreStats()).(*pdpb.StoreStats)
+	newStats := typeutil.DeepClone(store.GetStoreStats(), core.StoreStatsFactory)
 	newStats.IsBusy = busy
 	newStore := store.Clone(
 		core.SetStoreStats(newStats),
@@ -509,7 +509,7 @@ func (mc *Cluster) UpdateStoreRegionWeight(storeID uint64, weight float64) {
 // UpdateStoreLeaderSize updates store leader size.
 func (mc *Cluster) UpdateStoreLeaderSize(storeID uint64, size int64) {
 	store := mc.GetStore(storeID)
-	newStats := proto.Clone(store.GetStoreStats()).(*pdpb.StoreStats)
+	newStats := typeutil.DeepClone(store.GetStoreStats(), core.StoreStatsFactory)
 	newStats.Available = newStats.Capacity - uint64(store.GetLeaderSize())
 	newStore := store.Clone(
 		core.SetStoreStats(newStats),
@@ -521,7 +521,7 @@ func (mc *Cluster) UpdateStoreLeaderSize(storeID uint64, size int64) {
 // UpdateStoreRegionSize updates store region size.
 func (mc *Cluster) UpdateStoreRegionSize(storeID uint64, size int64) {
 	store := mc.GetStore(storeID)
-	newStats := proto.Clone(store.GetStoreStats()).(*pdpb.StoreStats)
+	newStats := typeutil.DeepClone(store.GetStoreStats(), core.StoreStatsFactory)
 	newStats.Available = newStats.Capacity - uint64(store.GetRegionSize())
 	newStore := store.Clone(
 		core.SetStoreStats(newStats),
@@ -642,7 +642,7 @@ func (mc *Cluster) UpdateStorageWriteQuery(storeID uint64, queryWrite uint64) {
 
 func (mc *Cluster) updateStorageStatistics(storeID uint64, update func(*pdpb.StoreStats)) {
 	store := mc.GetStore(storeID)
-	newStats := proto.Clone(store.GetStoreStats()).(*pdpb.StoreStats)
+	newStats := typeutil.DeepClone(store.GetStoreStats(), core.StoreStatsFactory)
 	update(newStats)
 	now := time.Now().Second()
 	interval := &pdpb.TimeInterval{StartTimestamp: uint64(now - statistics.StoreHeartBeatReportInterval), EndTimestamp: uint64(now)}
diff --git a/pkg/typeutil/clone.go b/pkg/typeutil/clone.go
new file mode 100644
index 000000000..c8e27b1c2
--- /dev/null
+++ b/pkg/typeutil/clone.go
@@ -0,0 +1,41 @@
+// Copyright 2022 TiKV Project Authors.
+//
+// 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 typeutil
+
+import (
+	"reflect"
+)
+
+// Codec is the interface representing objects that can marshal and unmarshal themselves.
+type Codec interface {
+	Marshal() (data []byte, err error)
+	Unmarshal(data []byte) error
+}
+
+// DeepClone returns the deep copy of the source
+func DeepClone[T Codec](src T, factory func() T) T {
+	if reflect.ValueOf(src).IsNil() {
+		var dst T
+		return dst
+	}
+	b, err := src.Marshal()
+	if err != nil {
+		var dst T
+		return dst
+	}
+	dst := factory()
+	dst.Unmarshal(b)
+	return dst
+}
diff --git a/server/cluster/cluster.go b/server/cluster/cluster.go
index 5fcbfc33d..454c64081 100644
--- a/server/cluster/cluster.go
+++ b/server/cluster/cluster.go
@@ -25,7 +25,6 @@ import (
 	"time"
 
 	"github.com/coreos/go-semver/semver"
-	"github.com/gogo/protobuf/proto"
 	"github.com/pingcap/errors"
 	"github.com/pingcap/failpoint"
 	"github.com/pingcap/kvproto/pkg/metapb"
@@ -1048,7 +1047,7 @@ func (c *RaftCluster) UpdateStoreLabels(storeID uint64, labels []*metapb.StoreLa
 	if store == nil {
 		return errs.ErrInvalidStoreID.FastGenByArgs(storeID)
 	}
-	newStore := proto.Clone(store.GetMeta()).(*metapb.Store)
+	newStore := typeutil.DeepClone(store.GetMeta(), core.StoreFactory)
 	if force {
 		newStore.Labels = labels
 	} else {
@@ -1065,7 +1064,7 @@ func (c *RaftCluster) DeleteStoreLabel(storeID uint64, labelKey string) error {
 	if store == nil {
 		return errs.ErrInvalidStoreID.FastGenByArgs(storeID)
 	}
-	newStore := proto.Clone(store.GetMeta()).(*metapb.Store)
+	newStore := typeutil.DeepClone(store.GetMeta(), core.StoreFactory)
 	labels := make([]*metapb.StoreLabel, 0, len(newStore.GetLabels())-1)
 	for _, label := range newStore.GetLabels() {
 		if label.Key == labelKey {
@@ -1960,7 +1959,7 @@ func (c *RaftCluster) changedRegionNotifier() <-chan *core.RegionInfo {
 func (c *RaftCluster) GetMetaCluster() *metapb.Cluster {
 	c.RLock()
 	defer c.RUnlock()
-	return proto.Clone(c.meta).(*metapb.Cluster)
+	return typeutil.DeepClone(c.meta, core.ClusterFactory)
 }
 
 // PutMetaCluster puts meta cluster.
@@ -1970,7 +1969,7 @@ func (c *RaftCluster) PutMetaCluster(meta *metapb.Cluster) error {
 	if meta.GetId() != c.clusterID {
 		return errors.Errorf("invalid cluster %v, mismatch cluster id %d", meta, c.clusterID)
 	}
-	return c.putMetaLocked(proto.Clone(meta).(*metapb.Cluster))
+	return c.putMetaLocked(typeutil.DeepClone(meta, core.ClusterFactory))
 }
 
 // GetRegionStats returns region statistics from cluster.
diff --git a/server/cluster/cluster_worker.go b/server/cluster/cluster_worker.go
index ed5f02ebf..07be76f07 100644
--- a/server/cluster/cluster_worker.go
+++ b/server/cluster/cluster_worker.go
@@ -17,13 +17,13 @@ package cluster
 import (
 	"bytes"
 
-	"github.com/gogo/protobuf/proto"
 	"github.com/pingcap/errors"
 	"github.com/pingcap/kvproto/pkg/metapb"
 	"github.com/pingcap/kvproto/pkg/pdpb"
 	"github.com/pingcap/log"
 	"github.com/tikv/pd/pkg/errs"
 	"github.com/tikv/pd/pkg/logutil"
+	"github.com/tikv/pd/pkg/typeutil"
 	"github.com/tikv/pd/server/core"
 	"github.com/tikv/pd/server/schedule"
 	"github.com/tikv/pd/server/statistics/buckets"
@@ -210,7 +210,7 @@ func (c *RaftCluster) HandleReportSplit(request *pdpb.ReportSplitRequest) (*pdpb
 	}
 
 	// Build origin region by using left and right.
-	originRegion := proto.Clone(right).(*metapb.Region)
+	originRegion := typeutil.DeepClone(right, core.RegionFactory)
 	originRegion.RegionEpoch = nil
 	originRegion.StartKey = left.GetStartKey()
 	log.Info("region split, generate new region",
@@ -232,7 +232,7 @@ func (c *RaftCluster) HandleBatchReportSplit(request *pdpb.ReportBatchSplitReque
 		return nil, err
 	}
 	last := len(regions) - 1
-	originRegion := proto.Clone(regions[last]).(*metapb.Region)
+	originRegion := typeutil.DeepClone(regions[last], core.RegionFactory)
 	hrm = core.RegionsToHexMeta(regions[:last])
 	log.Info("region batch split, generate new regions",
 		zap.Uint64("region-id", originRegion.GetId()),
diff --git a/server/core/factory.go b/server/core/factory.go
new file mode 100644
index 000000000..0537cae5b
--- /dev/null
+++ b/server/core/factory.go
@@ -0,0 +1,48 @@
+// Copyright 2022 TiKV Project Authors.
+//
+// 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 core
+
+import (
+	"github.com/pingcap/kvproto/pkg/encryptionpb"
+	"github.com/pingcap/kvproto/pkg/metapb"
+	"github.com/pingcap/kvproto/pkg/pdpb"
+)
+
+var (
+	// RegionFactory returns new region.
+	RegionFactory = func() *metapb.Region { return &metapb.Region{} }
+	// RegionPeerFactory returns new region peer.
+	RegionPeerFactory = func() *metapb.Peer { return &metapb.Peer{} }
+	// RegionHeartbeatResponseFactory returns new region heartbeat response.
+	RegionHeartbeatResponseFactory = func() *pdpb.RegionHeartbeatResponse { return &pdpb.RegionHeartbeatResponse{} }
+	// PeerStatsFactory returns new peer stats.
+	PeerStatsFactory = func() *pdpb.PeerStats { return &pdpb.PeerStats{} }
+
+	// StoreFactory returns new store.
+	StoreFactory = func() *metapb.Store { return &metapb.Store{} }
+	// StoreStatsFactory returns new store stats.
+	StoreStatsFactory = func() *pdpb.StoreStats { return &pdpb.StoreStats{} }
+
+	// KeyDictionaryFactory returns new key dictionary.
+	KeyDictionaryFactory = func() *encryptionpb.KeyDictionary { return &encryptionpb.KeyDictionary{} }
+	// MemberFactory returns new member.
+	MemberFactory = func() *pdpb.Member { return &pdpb.Member{} }
+	// ClusterFactory returns new cluster.
+	ClusterFactory = func() *metapb.Cluster { return &metapb.Cluster{} }
+	// TimeIntervalFactory returns new time interval.
+	TimeIntervalFactory = func() *pdpb.TimeInterval { return &pdpb.TimeInterval{} }
+	// QueryStatsFactory returns new query stats.
+	QueryStatsFactory = func() *pdpb.QueryStats { return &pdpb.QueryStats{} }
+)
diff --git a/server/core/region.go b/server/core/region.go
index ae817292d..32d8a3ac2 100644
--- a/server/core/region.go
+++ b/server/core/region.go
@@ -32,6 +32,7 @@ import (
 	"github.com/pingcap/kvproto/pkg/replication_modepb"
 	"github.com/pingcap/log"
 	"github.com/tikv/pd/pkg/logutil"
+	"github.com/tikv/pd/pkg/typeutil"
 	"go.uber.org/zap"
 )
 
@@ -194,17 +195,17 @@ func (r *RegionInfo) Inherit(origin *RegionInfo, bucketEnable bool) {
 func (r *RegionInfo) Clone(opts ...RegionCreateOption) *RegionInfo {
 	downPeers := make([]*pdpb.PeerStats, 0, len(r.downPeers))
 	for _, peer := range r.downPeers {
-		downPeers = append(downPeers, proto.Clone(peer).(*pdpb.PeerStats))
+		downPeers = append(downPeers, typeutil.DeepClone(peer, PeerStatsFactory))
 	}
 	pendingPeers := make([]*metapb.Peer, 0, len(r.pendingPeers))
 	for _, peer := range r.pendingPeers {
-		pendingPeers = append(pendingPeers, proto.Clone(peer).(*metapb.Peer))
+		pendingPeers = append(pendingPeers, typeutil.DeepClone(peer, RegionPeerFactory))
 	}
 
 	region := &RegionInfo{
 		term:              r.term,
-		meta:              proto.Clone(r.meta).(*metapb.Region),
-		leader:            proto.Clone(r.leader).(*metapb.Peer),
+		meta:              typeutil.DeepClone(r.meta, RegionFactory),
+		leader:            typeutil.DeepClone(r.leader, RegionPeerFactory),
 		downPeers:         downPeers,
 		pendingPeers:      pendingPeers,
 		writtenBytes:      r.writtenBytes,
@@ -213,10 +214,10 @@ func (r *RegionInfo) Clone(opts ...RegionCreateOption) *RegionInfo {
 		readKeys:          r.readKeys,
 		approximateSize:   r.approximateSize,
 		approximateKeys:   r.approximateKeys,
-		interval:          proto.Clone(r.interval).(*pdpb.TimeInterval),
+		interval:          typeutil.DeepClone(r.interval, TimeIntervalFactory),
 		replicationStatus: r.replicationStatus,
 		buckets:           r.buckets,
-		queryStats:        proto.Clone(r.queryStats).(*pdpb.QueryStats),
+		queryStats:        typeutil.DeepClone(r.queryStats, QueryStatsFactory),
 	}
 
 	for _, opt := range opts {
@@ -991,7 +992,7 @@ func (r *RegionsInfo) GetStoreWriteRate(storeID uint64) (bytesRate, keysRate flo
 func (r *RegionsInfo) GetMetaRegions() []*metapb.Region {
 	regions := make([]*metapb.Region, 0, r.regions.Len())
 	for _, item := range r.regions {
-		regions = append(regions, proto.Clone(item.region.meta).(*metapb.Region))
+		regions = append(regions, typeutil.DeepClone(item.region.meta, RegionFactory))
 	}
 	return regions
 }
@@ -1334,7 +1335,7 @@ type HexRegionMeta struct {
 }
 
 func (h HexRegionMeta) String() string {
-	var meta = proto.Clone(h.Region).(*metapb.Region)
+	meta := typeutil.DeepClone(h.Region, RegionFactory)
 	meta.StartKey = HexRegionKey(meta.StartKey)
 	meta.EndKey = HexRegionKey(meta.EndKey)
 	return strings.TrimSpace(proto.CompactTextString(meta))
@@ -1355,10 +1356,9 @@ type HexRegionsMeta []*metapb.Region
 func (h HexRegionsMeta) String() string {
 	var b strings.Builder
 	for _, r := range h {
-		meta := proto.Clone(r).(*metapb.Region)
+		meta := typeutil.DeepClone(r, RegionFactory)
 		meta.StartKey = HexRegionKey(meta.StartKey)
 		meta.EndKey = HexRegionKey(meta.EndKey)
-
 		b.WriteString(proto.CompactTextString(meta))
 	}
 	return strings.TrimSpace(b.String())
diff --git a/server/core/store.go b/server/core/store.go
index c57bfbe99..c51ca5db6 100644
--- a/server/core/store.go
+++ b/server/core/store.go
@@ -23,6 +23,7 @@ import (
 	"github.com/pingcap/kvproto/pkg/metapb"
 	"github.com/pingcap/log"
 	"github.com/tikv/pd/pkg/errs"
+	"github.com/tikv/pd/pkg/typeutil"
 	"github.com/tikv/pd/server/core/storelimit"
 	"go.uber.org/zap"
 )
@@ -76,17 +77,10 @@ func NewStoreInfo(store *metapb.Store, opts ...StoreCreateOption) *StoreInfo {
 	return storeInfo
 }
 
-func (s *StoreInfo) cloneMetaStore() *metapb.Store {
-	b, _ := s.meta.Marshal()
-	store := &metapb.Store{}
-	store.Unmarshal(b)
-	return store
-}
-
 // Clone creates a copy of current StoreInfo.
 func (s *StoreInfo) Clone(opts ...StoreCreateOption) *StoreInfo {
 	store := &StoreInfo{
-		meta:                s.cloneMetaStore(),
+		meta:                typeutil.DeepClone(s.meta, StoreFactory),
 		storeStats:          s.storeStats,
 		pauseLeaderTransfer: s.pauseLeaderTransfer,
 		slowStoreEvicted:    s.slowStoreEvicted,
diff --git a/server/core/store_option.go b/server/core/store_option.go
index e95374551..9513f8989 100644
--- a/server/core/store_option.go
+++ b/server/core/store_option.go
@@ -19,6 +19,7 @@ import (
 
 	"github.com/pingcap/kvproto/pkg/metapb"
 	"github.com/pingcap/kvproto/pkg/pdpb"
+	"github.com/tikv/pd/pkg/typeutil"
 	"github.com/tikv/pd/server/core/storelimit"
 )
 
@@ -28,7 +29,7 @@ type StoreCreateOption func(region *StoreInfo)
 // SetStoreAddress sets the address for the store.
 func SetStoreAddress(address, statusAddress, peerAddress string) StoreCreateOption {
 	return func(store *StoreInfo) {
-		meta := store.cloneMetaStore()
+		meta := typeutil.DeepClone(store.meta, StoreFactory)
 		meta.Address = address
 		meta.StatusAddress = statusAddress
 		meta.PeerAddress = peerAddress
@@ -39,7 +40,7 @@ func SetStoreAddress(address, statusAddress, peerAddress string) StoreCreateOpti
 // SetStoreLabels sets the labels for the store.
 func SetStoreLabels(labels []*metapb.StoreLabel) StoreCreateOption {
 	return func(store *StoreInfo) {
-		meta := store.cloneMetaStore()
+		meta := typeutil.DeepClone(store.meta, StoreFactory)
 		meta.Labels = labels
 		store.meta = meta
 	}
@@ -48,7 +49,7 @@ func SetStoreLabels(labels []*metapb.StoreLabel) StoreCreateOption {
 // SetStoreStartTime sets the start timestamp for the store.
 func SetStoreStartTime(startTS int64) StoreCreateOption {
 	return func(store *StoreInfo) {
-		meta := store.cloneMetaStore()
+		meta := typeutil.DeepClone(store.meta, StoreFactory)
 		meta.StartTimestamp = startTS
 		store.meta = meta
 	}
@@ -57,7 +58,7 @@ func SetStoreStartTime(startTS int64) StoreCreateOption {
 // SetStoreVersion sets the version for the store.
 func SetStoreVersion(githash, version string) StoreCreateOption {
 	return func(store *StoreInfo) {
-		meta := store.cloneMetaStore()
+		meta := typeutil.DeepClone(store.meta, StoreFactory)
 		meta.Version = version
 		meta.GitHash = githash
 		store.meta = meta
@@ -67,7 +68,7 @@ func SetStoreVersion(githash, version string) StoreCreateOption {
 // SetStoreDeployPath sets the deploy path for the store.
 func SetStoreDeployPath(deployPath string) StoreCreateOption {
 	return func(store *StoreInfo) {
-		meta := store.cloneMetaStore()
+		meta := typeutil.DeepClone(store.meta, StoreFactory)
 		meta.DeployPath = deployPath
 		store.meta = meta
 	}
@@ -76,7 +77,7 @@ func SetStoreDeployPath(deployPath string) StoreCreateOption {
 // OfflineStore offline a store
 func OfflineStore(physicallyDestroyed bool) StoreCreateOption {
 	return func(store *StoreInfo) {
-		meta := store.cloneMetaStore()
+		meta := typeutil.DeepClone(store.meta, StoreFactory)
 		meta.State = metapb.StoreState_Offline
 		meta.NodeState = metapb.NodeState_Removing
 		meta.PhysicallyDestroyed = physicallyDestroyed
@@ -87,7 +88,7 @@ func OfflineStore(physicallyDestroyed bool) StoreCreateOption {
 // UpStore up a store
 func UpStore() StoreCreateOption {
 	return func(store *StoreInfo) {
-		meta := store.cloneMetaStore()
+		meta := typeutil.DeepClone(store.meta, StoreFactory)
 		meta.State = metapb.StoreState_Up
 		meta.NodeState = metapb.NodeState_Serving
 		store.meta = meta
@@ -97,7 +98,7 @@ func UpStore() StoreCreateOption {
 // TombstoneStore set a store to tombstone.
 func TombstoneStore() StoreCreateOption {
 	return func(store *StoreInfo) {
-		meta := store.cloneMetaStore()
+		meta := typeutil.DeepClone(store.meta, StoreFactory)
 		meta.State = metapb.StoreState_Tombstone
 		meta.NodeState = metapb.NodeState_Removed
 		store.meta = meta
diff --git a/server/schedule/filter/filters.go b/server/schedule/filter/filters.go
index ae300aaed..bb5273d63 100644
--- a/server/schedule/filter/filters.go
+++ b/server/schedule/filter/filters.go
@@ -18,10 +18,10 @@ import (
 	"fmt"
 	"strconv"
 
-	"github.com/gogo/protobuf/proto"
 	"github.com/pingcap/kvproto/pkg/metapb"
 	"github.com/pingcap/log"
 	"github.com/tikv/pd/pkg/slice"
+	"github.com/tikv/pd/pkg/typeutil"
 	"github.com/tikv/pd/server/config"
 	"github.com/tikv/pd/server/core"
 	"github.com/tikv/pd/server/core/storelimit"
@@ -846,7 +846,7 @@ func (f *isolationFilter) Target(opt *config.PersistOptions, store *core.StoreIn
 // FitRegion in filter
 func createRegionForRuleFit(startKey, endKey []byte,
 	peers []*metapb.Peer, leader *metapb.Peer, opts ...core.RegionCreateOption) *core.RegionInfo {
-	copyLeader := proto.Clone(leader).(*metapb.Peer)
+	copyLeader := typeutil.DeepClone(leader, core.RegionPeerFactory)
 	copyPeers := make([]*metapb.Peer, 0, len(peers))
 	for _, p := range peers {
 		peer := &metapb.Peer{
diff --git a/server/server.go b/server/server.go
index ccc513608..8caddcce2 100644
--- a/server/server.go
+++ b/server/server.go
@@ -30,7 +30,6 @@ import (
 	"time"
 
 	"github.com/coreos/go-semver/semver"
-	"github.com/gogo/protobuf/proto"
 	"github.com/gorilla/mux"
 	"github.com/pingcap/errors"
 	"github.com/pingcap/failpoint"
@@ -736,7 +735,7 @@ func (s *Server) GetClientScheme() string {
 
 // GetMemberInfo returns the server member information.
 func (s *Server) GetMemberInfo() *pdpb.Member {
-	return proto.Clone(s.member.Member()).(*pdpb.Member)
+	return typeutil.DeepClone(s.member.Member(), core.MemberFactory)
 }
 
 // GetHandler returns the handler for API.
diff --git a/tools/pd-simulator/simulator/client.go b/tools/pd-simulator/simulator/client.go
index 1306af648..331bc830b 100644
--- a/tools/pd-simulator/simulator/client.go
+++ b/tools/pd-simulator/simulator/client.go
@@ -20,10 +20,10 @@ import (
 	"sync"
 	"time"
 
-	"github.com/gogo/protobuf/proto"
 	"github.com/pingcap/errors"
 	"github.com/pingcap/kvproto/pkg/metapb"
 	"github.com/pingcap/kvproto/pkg/pdpb"
+	"github.com/tikv/pd/pkg/typeutil"
 	"github.com/tikv/pd/server/core"
 	"github.com/tikv/pd/tools/pd-simulator/simulator/simutil"
 	"go.uber.org/zap"
@@ -269,10 +269,13 @@ func (c *client) Bootstrap(ctx context.Context, store *metapb.Store, region *met
 	if err != nil {
 		return err
 	}
+	newStore := typeutil.DeepClone(store, core.StoreFactory)
+	newRegion := typeutil.DeepClone(region, core.RegionFactory)
+
 	res, err := c.pdClient().Bootstrap(ctx, &pdpb.BootstrapRequest{
 		Header: c.requestHeader(),
-		Store:  proto.Clone(store).(*metapb.Store),
-		Region: proto.Clone(region).(*metapb.Region),
+		Store:  newStore,
+		Region: newRegion,
 	})
 	if err != nil {
 		return err
@@ -285,9 +288,10 @@ func (c *client) Bootstrap(ctx context.Context, store *metapb.Store, region *met
 
 func (c *client) PutStore(ctx context.Context, store *metapb.Store) error {
 	ctx, cancel := context.WithTimeout(ctx, pdTimeout)
+	newStore := typeutil.DeepClone(store, core.StoreFactory)
 	resp, err := c.pdClient().PutStore(ctx, &pdpb.PutStoreRequest{
 		Header: c.requestHeader(),
-		Store:  proto.Clone(store).(*metapb.Store),
+		Store:  newStore,
 	})
 	cancel()
 	if err != nil {
@@ -302,9 +306,10 @@ func (c *client) PutStore(ctx context.Context, store *metapb.Store) error {
 
 func (c *client) StoreHeartbeat(ctx context.Context, stats *pdpb.StoreStats) error {
 	ctx, cancel := context.WithTimeout(ctx, pdTimeout)
+	newStats := typeutil.DeepClone(stats, core.StoreStatsFactory)
 	resp, err := c.pdClient().StoreHeartbeat(ctx, &pdpb.StoreHeartbeatRequest{
 		Header: c.requestHeader(),
-		Stats:  proto.Clone(stats).(*pdpb.StoreStats),
+		Stats:  newStats,
 	})
 	cancel()
 	if err != nil {


Test patch:
diff --git a/pkg/mock/mockhbstream/mockhbstream_test.go b/pkg/mock/mockhbstream/mockhbstream_test.go
index f31fea5b5..ad87d75cd 100644
--- a/pkg/mock/mockhbstream/mockhbstream_test.go
+++ b/pkg/mock/mockhbstream/mockhbstream_test.go
@@ -18,14 +18,15 @@ import (
 	"context"
 	"testing"
 
-	"github.com/gogo/protobuf/proto"
 	"github.com/pingcap/kvproto/pkg/eraftpb"
 	"github.com/pingcap/kvproto/pkg/metapb"
 	"github.com/pingcap/kvproto/pkg/pdpb"
 	"github.com/stretchr/testify/require"
 	"github.com/tikv/pd/pkg/mock/mockcluster"
 	"github.com/tikv/pd/pkg/testutil"
+	"github.com/tikv/pd/pkg/typeutil"
 	"github.com/tikv/pd/server/config"
+	"github.com/tikv/pd/server/core"
 	"github.com/tikv/pd/server/schedule/hbstream"
 )
 
@@ -50,13 +51,15 @@ func TestActivity(t *testing.T) {
 	// Active stream is stream1.
 	hbs.BindStream(1, stream1)
 	testutil.Eventually(re, func() bool {
-		hbs.SendMsg(region, proto.Clone(msg).(*pdpb.RegionHeartbeatResponse))
+		newMsg := typeutil.DeepClone(msg, core.RegionHeartbeatResponseFactory)
+		hbs.SendMsg(region, newMsg)
 		return stream1.Recv() != nil && stream2.Recv() == nil
 	})
 	// Rebind to stream2.
 	hbs.BindStream(1, stream2)
 	testutil.Eventually(re, func() bool {
-		hbs.SendMsg(region, proto.Clone(msg).(*pdpb.RegionHeartbeatResponse))
+		newMsg := typeutil.DeepClone(msg, core.RegionHeartbeatResponseFactory)
+		hbs.SendMsg(region, newMsg)
 		return stream1.Recv() == nil && stream2.Recv() != nil
 	})
 	// SendErr to stream2.
@@ -67,7 +70,8 @@ func TestActivity(t *testing.T) {
 	// Switch back to 1 again.
 	hbs.BindStream(1, stream1)
 	testutil.Eventually(re, func() bool {
-		hbs.SendMsg(region, proto.Clone(msg).(*pdpb.RegionHeartbeatResponse))
+		newMsg := typeutil.DeepClone(msg, core.RegionHeartbeatResponseFactory)
+		hbs.SendMsg(region, newMsg)
 		return stream1.Recv() != nil && stream2.Recv() == nil
 	})
 }
diff --git a/pkg/typeutil/clone_test.go b/pkg/typeutil/clone_test.go
new file mode 100644
index 000000000..f7a7ef5d5
--- /dev/null
+++ b/pkg/typeutil/clone_test.go
@@ -0,0 +1,38 @@
+package typeutil
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+type fate struct {
+	ID   uint64
+	Attr struct {
+		Age int64
+	}
+}
+
+func (f *fate) Marshal() ([]byte, error) {
+	return json.Marshal(f)
+}
+
+func (f *fate) Unmarshal(data []byte) error {
+	return json.Unmarshal(data, f)
+}
+
+var fateFactory = func() *fate { return &fate{} }
+
+func TestDeepClone(t *testing.T) {
+	re := assert.New(t)
+	src := &fate{ID: 1}
+	dst := DeepClone(src, fateFactory)
+	re.EqualValues(dst.ID, 1)
+	dst.ID = 2
+	re.EqualValues(src.ID, 1)
+
+	// case2: the source is nil
+	var src2 *fate
+	re.Nil(DeepClone(src2, fateFactory))
+}
diff --git a/server/api/store_test.go b/server/api/store_test.go
index a06de1f27..87a78089f 100644
--- a/server/api/store_test.go
+++ b/server/api/store_test.go
@@ -25,12 +25,12 @@ import (
 	"time"
 
 	"github.com/docker/go-units"
-	"github.com/gogo/protobuf/proto"
 	"github.com/pingcap/kvproto/pkg/metapb"
 	"github.com/pingcap/kvproto/pkg/pdpb"
 	"github.com/stretchr/testify/require"
 	"github.com/stretchr/testify/suite"
 	tu "github.com/tikv/pd/pkg/testutil"
+	"github.com/tikv/pd/pkg/typeutil"
 	"github.com/tikv/pd/server"
 	"github.com/tikv/pd/server/config"
 	"github.com/tikv/pd/server/core"
@@ -124,8 +124,8 @@ func checkStoresInfo(re *require.Assertions, ss []*StoreInfo, want []*metapb.Sto
 		}
 	}
 	for _, s := range ss {
-		obtained := proto.Clone(s.Store.Store).(*metapb.Store)
-		expected := proto.Clone(mapWant[obtained.Id]).(*metapb.Store)
+		obtained := typeutil.DeepClone(s.Store.Store, core.StoreFactory)
+		expected := typeutil.DeepClone(mapWant[obtained.Id], core.StoreFactory)
 		// Ignore lastHeartbeat
 		obtained.LastHeartbeat, expected.LastHeartbeat = 0, 0
 		re.Equal(expected, obtained)
diff --git a/server/core/factory_test.go b/server/core/factory_test.go
new file mode 100644
index 000000000..a027f6d47
--- /dev/null
+++ b/server/core/factory_test.go
@@ -0,0 +1,43 @@
+// Copyright 2022 TiKV Project Authors.
+//
+// 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 core
+
+import (
+	"testing"
+
+	"github.com/gogo/protobuf/proto"
+	"github.com/pingcap/kvproto/pkg/metapb"
+	"github.com/tikv/pd/pkg/typeutil"
+)
+
+func BenchmarkDeepClone(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		src := &metapb.Region{Id: 1}
+		dst := typeutil.DeepClone(src, RegionFactory)
+		dst.Id = 1
+	}
+}
+
+func BenchmarkProtoClone(b *testing.B) {
+	clone := func(src *metapb.Region) *metapb.Region {
+		dst := proto.Clone(src).(*metapb.Region)
+		return dst
+	}
+	for i := 0; i < b.N; i++ {
+		src := &metapb.Region{Id: 1}
+		dst := clone(src)
+		dst.Id = 1
+	}
+}
diff --git a/server/core/store_test.go b/server/core/store_test.go
index 24c4972db..fa24121f4 100644
--- a/server/core/store_test.go
+++ b/server/core/store_test.go
@@ -24,6 +24,7 @@ import (
 	"github.com/pingcap/kvproto/pkg/metapb"
 	"github.com/pingcap/kvproto/pkg/pdpb"
 	"github.com/stretchr/testify/require"
+	"github.com/tikv/pd/pkg/typeutil"
 )
 
 func TestDistinctScore(t *testing.T) {
@@ -94,7 +95,7 @@ func TestCloneStore(t *testing.T) {
 func TestCloneMetaStore(t *testing.T) {
 	re := require.New(t)
 	store := &metapb.Store{Id: 1, Address: "mock://tikv-1", Labels: []*metapb.StoreLabel{{Key: "zone", Value: "z1"}, {Key: "host", Value: "h1"}}}
-	store2 := NewStoreInfo(store).cloneMetaStore()
+	store2 := typeutil.DeepClone(NewStoreInfo(store).meta, StoreFactory)
 	re.Equal(store2.Labels, store.Labels)
 	store2.Labels[0].Value = "changed value"
 	re.NotEqual(store2.Labels, store.Labels)
diff --git a/server/encryptionkm/key_manager_test.go b/server/encryptionkm/key_manager_test.go
index 3ca8bb320..714b4f1f9 100644
--- a/server/encryptionkm/key_manager_test.go
+++ b/server/encryptionkm/key_manager_test.go
@@ -32,6 +32,7 @@ import (
 	"github.com/tikv/pd/pkg/etcdutil"
 	"github.com/tikv/pd/pkg/tempurl"
 	"github.com/tikv/pd/pkg/typeutil"
+	"github.com/tikv/pd/server/core"
 	"github.com/tikv/pd/server/election"
 	"go.etcd.io/etcd/clientv3"
 	"go.etcd.io/etcd/embed"
@@ -295,9 +296,10 @@ func TestGetKey(t *testing.T) {
 	// Get key that require a reload.
 	// Deliberately cancel watcher, delete a key and check if it has reloaded.
 	loadedKeys := m.keys.Load().(*encryptionpb.KeyDictionary)
-	loadedKeys = proto.Clone(loadedKeys).(*encryptionpb.KeyDictionary)
-	delete(loadedKeys.Keys, 456)
-	m.keys.Store(loadedKeys)
+	newLoadedKeys := typeutil.DeepClone(loadedKeys, core.KeyDictionaryFactory)
+
+	delete(newLoadedKeys.Keys, 456)
+	m.keys.Store(newLoadedKeys)
 	m.mu.keysRevision = 0
 	key, err = m.GetKey(uint64(456))
 	re.NoError(err)
@@ -906,7 +908,7 @@ func TestSetLeadershipWithEncryptionDisabling(t *testing.T) {
 	re.NoError(err)
 	// Check encryption is disabled
 	<-reloadEvent
-	expectedKeys := proto.Clone(keys).(*encryptionpb.KeyDictionary)
+	expectedKeys := typeutil.DeepClone(keys, core.KeyDictionaryFactory)
 	expectedKeys.CurrentKeyId = disableEncryptionKeyID
 	expectedKeys.Keys[123].WasExposed = true
 	re.True(proto.Equal(m.keys.Load().(*encryptionpb.KeyDictionary), expectedKeys))
diff --git a/tests/client/client_test.go b/tests/client/client_test.go
index 9f7e565aa..0fa966d00 100644
--- a/tests/client/client_test.go
+++ b/tests/client/client_test.go
@@ -27,7 +27,6 @@ import (
 	"testing"
 	"time"
 
-	"github.com/gogo/protobuf/proto"
 	"github.com/pingcap/failpoint"
 	"github.com/pingcap/kvproto/pkg/metapb"
 	"github.com/pingcap/kvproto/pkg/pdpb"
@@ -38,6 +37,7 @@ import (
 	"github.com/tikv/pd/pkg/mock/mockid"
 	"github.com/tikv/pd/pkg/testutil"
 	"github.com/tikv/pd/pkg/tsoutil"
+	"github.com/tikv/pd/pkg/typeutil"
 	"github.com/tikv/pd/server"
 	"github.com/tikv/pd/server/config"
 	"github.com/tikv/pd/server/core"
@@ -1064,7 +1064,7 @@ func (suite *clientTestSuite) TestGetStore() {
 	// Mark the store as offline.
 	err = cluster.RemoveStore(store.GetId(), false)
 	suite.NoError(err)
-	offlineStore := proto.Clone(store).(*metapb.Store)
+	offlineStore := typeutil.DeepClone(store, core.StoreFactory)
 	offlineStore.State = metapb.StoreState_Offline
 	offlineStore.NodeState = metapb.NodeState_Removing
 
diff --git a/tests/client/go.mod b/tests/client/go.mod
index 7a68d40df..cee2b19e8 100644
--- a/tests/client/go.mod
+++ b/tests/client/go.mod
@@ -3,7 +3,7 @@ module github.com/tikv/pd/tests/client
 go 1.18
 
 require (
-	github.com/gogo/protobuf v1.3.2
+	github.com/gogo/protobuf v1.3.2 // indirect
 	github.com/pingcap/failpoint v0.0.0-20210918120811-547c13e3eb00
 	github.com/pingcap/kvproto v0.0.0-20220818063303-5c20f55db5ad
 	github.com/stretchr/testify v1.7.0
diff --git a/tests/pdctl/helper.go b/tests/pdctl/helper.go
index 5691dde66..534035752 100644
--- a/tests/pdctl/helper.go
+++ b/tests/pdctl/helper.go
@@ -20,11 +20,11 @@ import (
 	"fmt"
 	"sort"
 
-	"github.com/gogo/protobuf/proto"
 	"github.com/pingcap/kvproto/pkg/metapb"
 	"github.com/pingcap/kvproto/pkg/pdpb"
 	"github.com/spf13/cobra"
 	"github.com/stretchr/testify/require"
+	"github.com/tikv/pd/pkg/typeutil"
 	"github.com/tikv/pd/server"
 	"github.com/tikv/pd/server/api"
 	"github.com/tikv/pd/server/core"
@@ -52,8 +52,8 @@ func CheckStoresInfo(re *require.Assertions, stores []*api.StoreInfo, want []*ap
 		}
 	}
 	for _, s := range stores {
-		obtained := proto.Clone(s.Store.Store).(*metapb.Store)
-		expected := proto.Clone(mapWant[obtained.Id].Store.Store).(*metapb.Store)
+		obtained := typeutil.DeepClone(s.Store.Store, core.StoreFactory)
+		expected := typeutil.DeepClone(mapWant[obtained.Id].Store.Store, core.StoreFactory)
 		// Ignore state
 		obtained.State, expected.State = 0, 0
 		obtained.NodeState, expected.NodeState = 0, 0
diff --git a/tests/pdctl/hot/hot_test.go b/tests/pdctl/hot/hot_test.go
index 50f52f791..1a948df73 100644
--- a/tests/pdctl/hot/hot_test.go
+++ b/tests/pdctl/hot/hot_test.go
@@ -22,10 +22,10 @@ import (
 	"time"
 
 	"github.com/docker/go-units"
-	"github.com/gogo/protobuf/proto"
 	"github.com/pingcap/kvproto/pkg/metapb"
 	"github.com/pingcap/kvproto/pkg/pdpb"
 	"github.com/stretchr/testify/require"
+	"github.com/tikv/pd/pkg/typeutil"
 	"github.com/tikv/pd/server/api"
 	"github.com/tikv/pd/server/config"
 	"github.com/tikv/pd/server/core"
@@ -70,7 +70,8 @@ func TestHot(t *testing.T) {
 	// test hot store
 	ss := leaderServer.GetStore(1)
 	now := time.Now().Second()
-	newStats := proto.Clone(ss.GetStoreStats()).(*pdpb.StoreStats)
+
+	newStats := typeutil.DeepClone(ss.GetStoreStats(), core.StoreStatsFactory)
 	bytesWritten := uint64(8 * units.MiB)
 	bytesRead := uint64(16 * units.MiB)
 	keysWritten := uint64(2000)