-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
293 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
package deep | ||
|
||
import ( | ||
"reflect" | ||
) | ||
|
||
func Copy[T any](src, dst *T) { | ||
srv := reflect.ValueOf(src) | ||
srv = srv.Elem() | ||
drv := reflect.ValueOf(dst) | ||
drv = drv.Elem() | ||
h := copyHandler{addrMap: map[uint64]reflect.Value{}} | ||
h.handle(srv, drv) | ||
clear(h.addrMap) | ||
} | ||
|
||
type copyHandler struct { | ||
addrMap map[uint64]reflect.Value | ||
} | ||
|
||
func (copyHandler) genKey(src reflect.Value) uint64 { | ||
switch src.Kind() { | ||
case reflect.Pointer, reflect.Chan, reflect.Map, reflect.UnsafePointer, reflect.Func, reflect.Slice: | ||
return uint64(src.Pointer())<<6 + uint64(src.Kind())<<1 | ||
case reflect.Struct, reflect.Interface: | ||
return uint64(src.UnsafeAddr())<<6 + uint64(src.Kind())<<1 + 1 | ||
default: | ||
return 0 | ||
} | ||
} | ||
|
||
func (h copyHandler) handle(src, dst reflect.Value) { | ||
switch src.Kind() { | ||
case reflect.Struct: | ||
h.handleStruct(src, dst) | ||
case reflect.Interface: | ||
h.handleInterface(src, dst) | ||
case reflect.Pointer: | ||
h.handlePointer(src, dst) | ||
case reflect.Array: | ||
h.handleArray(src, dst) | ||
case reflect.Slice: | ||
h.handleSlice(src, dst) | ||
case reflect.Chan: | ||
dst.Set(reflect.MakeChan(src.Type(), src.Cap())) | ||
case reflect.Map: | ||
dst.Set(reflect.MakeMap(src.Type())) | ||
h.handleMap(src, dst) | ||
default: | ||
dst.Set(src) | ||
} | ||
} | ||
|
||
func (h copyHandler) handlePointer(src, dst reflect.Value) { | ||
src = src.Elem() | ||
addr := h.genKey(src) | ||
ndst, ok := h.addrMap[addr] | ||
if !ok { | ||
ndst = reflect.New(src.Type()).Elem() | ||
if addr > 0 { | ||
h.addrMap[addr] = ndst | ||
} | ||
h.handle(src, ndst) | ||
} | ||
dst.Set(ndst.Addr()) | ||
} | ||
|
||
func (h copyHandler) handleStruct(src, dst reflect.Value) { | ||
srcAddr := h.genKey(src) | ||
if srcAddr > 0 { | ||
h.addrMap[srcAddr] = dst | ||
} | ||
for i := 0; i < src.NumField(); i++ { | ||
srcf := src.Field(i) | ||
addr := h.genKey(srcf) | ||
dstf, ok := h.addrMap[addr] | ||
if !ok { | ||
ndstf := dst.Field(i) | ||
if addr > 0 { | ||
h.addrMap[addr] = ndstf | ||
} | ||
h.handle(srcf, ndstf) | ||
} else { | ||
dst.Field(i).Set(dstf) | ||
} | ||
} | ||
} | ||
|
||
func (h copyHandler) handleInterface(src, dst reflect.Value) { | ||
src = src.Elem() | ||
h.handle(src, dst) | ||
} | ||
|
||
func (h copyHandler) handleArray(src, dst reflect.Value) { | ||
for i := 0; i < src.Len(); i++ { | ||
srcf := src.Index(i) | ||
addr := h.genKey(srcf) | ||
dstf, ok := h.addrMap[addr] | ||
if !ok { | ||
ndstf := dst.Index(i) | ||
if addr > 0 { | ||
h.addrMap[addr] = ndstf | ||
} | ||
h.handle(srcf, ndstf) | ||
} else { | ||
dst.Index(i).Set(dstf) | ||
} | ||
} | ||
} | ||
|
||
func (h copyHandler) handleSlice(src, dst reflect.Value) { | ||
srcAddr := h.genKey(src) | ||
if srcAddr > 0 { | ||
h.addrMap[srcAddr] = dst | ||
} | ||
dst.Grow(src.Len() - dst.Len()) | ||
dst.SetLen(src.Len()) | ||
for i := 0; i < src.Len(); i++ { | ||
srcf := src.Index(i) | ||
addr := h.genKey(srcf) | ||
dstf, ok := h.addrMap[addr] | ||
if !ok { | ||
ndstf := dst.Index(i) | ||
if addr > 0 { | ||
h.addrMap[addr] = ndstf | ||
} | ||
h.handle(srcf, ndstf) | ||
} else { | ||
dst.Index(i).Set(dstf) | ||
} | ||
} | ||
} | ||
|
||
func (h copyHandler) handleMap(src, dst reflect.Value) { | ||
srcAddr := h.genKey(src) | ||
if srcAddr > 0 { | ||
h.addrMap[srcAddr] = dst | ||
} | ||
iter := src.MapRange() | ||
for iter.Next() { | ||
k := iter.Key() | ||
v := iter.Value() | ||
|
||
kAddr := h.genKey(k) | ||
kdst, ok := h.addrMap[kAddr] | ||
if !ok { | ||
kdst = reflect.New(k.Type()).Elem() | ||
if kAddr > 0 { | ||
h.addrMap[kAddr] = kdst | ||
} | ||
h.handle(k, kdst) | ||
} | ||
|
||
vAddr := h.genKey(v) | ||
vdst, ok := h.addrMap[vAddr] | ||
if !ok { | ||
vdst = reflect.New(v.Type()).Elem() | ||
if vAddr > 0 { | ||
h.addrMap[vAddr] = vdst | ||
} | ||
h.handle(v, vdst) | ||
} | ||
dst.SetMapIndex(kdst, vdst) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
package deep | ||
|
||
import ( | ||
"encoding/json" | ||
"testing" | ||
|
||
"github.com/bytedance/sonic" | ||
) | ||
|
||
type _T1 struct { | ||
T1V int `json:"t_1_v"` | ||
T1P2 *_T2 `json:"t_1_p_2"` | ||
T1P3 any `json:"t_1_p_3"` | ||
T1P4 [3]int `json:"t_1_p_4"` | ||
T1P5 [3]int `json:"t_1_p_5"` | ||
T1P6 *_T1 `json:"t_1_p_6"` | ||
T1P7 []int `json:"t_1_p_7"` | ||
T1P8 []int `json:"t_1_p_8"` | ||
T1P9 []*_T2 `json:"t_1_p_9"` | ||
T1P10 map[string]int `json:"t_1_p_10"` | ||
T1P11 map[string]int `json:"t_1_p_11"` | ||
T1P12 []int `json:"t_1_p_12"` | ||
} | ||
|
||
type _T2 struct { | ||
T2P1 *_T1 `json:"t_2_p_1"` | ||
T2V int `json:"t_2_v"` | ||
} | ||
|
||
func _assert(t *testing.T, a bool) { | ||
if !a { | ||
t.Fail() | ||
panic("deepcopy failed") | ||
} | ||
} | ||
|
||
func newSrc() *_T1 { | ||
src := &_T1{ | ||
T1V: 1, | ||
T1P2: nil, | ||
T1P3: nil, | ||
} | ||
src.T1P2 = &_T2{ | ||
T2P1: src, | ||
T2V: 2, | ||
} | ||
src.T1P3 = src.T1P2 | ||
src.T1P4 = [3]int{1, 2, 3} | ||
src.T1P5 = src.T1P4 | ||
src.T1P6 = src | ||
src.T1P7 = []int{1, 2, 3} | ||
src.T1P8 = src.T1P7 | ||
src.T1P9 = []*_T2{src.T1P2} | ||
src.T1P10 = map[string]int{"a": 1, "b": 2} | ||
src.T1P11 = src.T1P10 | ||
src.T1P12 = src.T1P4[:] | ||
return src | ||
} | ||
|
||
func Test_deep_copy(t *testing.T) { | ||
src := newSrc() | ||
dst := &_T1{} | ||
Copy(src, dst) | ||
_assert(t, dst.T1V == src.T1V) | ||
_assert(t, dst.T1P2.T2V == src.T1P2.T2V) | ||
_assert(t, dst.T1P2.T2P1.T1V == src.T1P2.T2P1.T1V) | ||
_assert(t, dst.T1P3.(*_T2).T2V == src.T1P3.(*_T2).T2V) | ||
_assert(t, dst.T1P3.(*_T2).T2P1.T1V == src.T1P3.(*_T2).T2P1.T1V) | ||
_assert(t, dst.T1P4[0] == src.T1P4[0]) | ||
_assert(t, dst.T1P4[1] == src.T1P4[1]) | ||
_assert(t, dst.T1P4[2] == src.T1P4[2]) | ||
_assert(t, dst.T1P5[0] == src.T1P5[0]) | ||
_assert(t, dst.T1P5[1] == src.T1P5[1]) | ||
_assert(t, dst.T1P5[2] == src.T1P5[2]) | ||
_assert(t, dst.T1P7[0] == src.T1P7[0]) | ||
_assert(t, dst.T1P7[1] == src.T1P7[1]) | ||
_assert(t, dst.T1P7[2] == src.T1P7[2]) | ||
_assert(t, dst.T1P8[0] == src.T1P8[0]) | ||
_assert(t, dst.T1P8[1] == src.T1P8[1]) | ||
_assert(t, dst.T1P8[2] == src.T1P8[2]) | ||
_assert(t, dst.T1P10["a"] == src.T1P10["a"]) | ||
_assert(t, dst.T1P10["b"] == src.T1P10["b"]) | ||
_assert(t, dst.T1P11["a"] == src.T1P11["a"]) | ||
_assert(t, dst.T1P11["b"] == src.T1P11["b"]) | ||
|
||
dst.T1V = 3 | ||
_assert(t, dst.T1V == dst.T1P2.T2P1.T1V) | ||
_assert(t, dst.T1V == dst.T1P3.(*_T2).T2P1.T1V) | ||
dst.T1P4[1] = 6 | ||
_assert(t, dst.T1P5[0] == dst.T1P4[0]) | ||
_assert(t, dst.T1P5[1] != dst.T1P4[1]) | ||
_assert(t, dst.T1P5[2] == dst.T1P4[2]) | ||
_assert(t, dst.T1P12[1] != dst.T1P4[1]) | ||
dst.T1P7[1] = 6 | ||
_assert(t, dst.T1P7[0] == dst.T1P8[0]) | ||
_assert(t, dst.T1P7[1] == dst.T1P8[1]) | ||
_assert(t, dst.T1P7[2] == dst.T1P8[2]) | ||
|
||
dst.T1P10["b"] = 3 | ||
_assert(t, dst.T1P11["a"] == 1) | ||
_assert(t, dst.T1P11["b"] == 3) | ||
} | ||
|
||
func Benchmark_copy_json(b *testing.B) { | ||
src := newSrc() | ||
for i := 0; i < b.N; i++ { | ||
dst := &_T1{} | ||
mid, _ := json.Marshal(src) | ||
_ = json.Unmarshal(mid, dst) | ||
} | ||
} | ||
|
||
func Benchmark_copy_sonic(b *testing.B) { | ||
src := newSrc() | ||
for i := 0; i < b.N; i++ { | ||
dst := &_T1{} | ||
mid, _ := sonic.Marshal(src) | ||
_ = sonic.Unmarshal(mid, dst) | ||
} | ||
} | ||
|
||
func Benchmark_copy_deep(b *testing.B) { | ||
src := newSrc() | ||
for i := 0; i < b.N; i++ { | ||
dst := &_T1{} | ||
Copy(src, dst) | ||
} | ||
} |