一次帮同事 review 代码,想在 go 里面找一个支持深度 Copy 的库,github 上少得可怜。最后找到 json marshal 加 unmarshal 的方式,但是这种方式有两个缺点,第 1 marshal 一次 reflect,unmarshal 一次 reflect,有两次 reflect 的过程,效率会垫底。第 2,不支持过滤条件,这点硬伤,改不了。特对这两点问题,所以想撸个改进版本(更快,更可控)。
https://github.com/antlabs/deepcopy
deepcopy.Copy 主要用于两个类型间的深度拷贝[从零实现]
go get github.com/antlabs/deepcopy
package main
import (
"fmt"
"github.com/antlabs/deepcopy"
)
type dst struct {
ID int
Result string
}
type src struct{
ID int
Text string
}
func main() {
d, s := dst{}, src{ID:3}
deepcopy.Copy(&d, &s).Do()
fmt.Printf("%#v\n", d)
}
如果 src 的结构体嵌套了两套,MaxDepth 可以控制只拷贝一层
deepcopy.Copy(&dst{}, &src{}).MaxDepth(1).Do()
只拷贝结构体里面有 copy tag 的字段,比如下面只会拷贝 ID 成员
package main
import (
"fmt"
"github.com/antlabs/deepcopy"
)
type dst struct {
ID int `copy:"ID"`
Result string
}
type src struct {
ID int `copy:"ID"`
Result string
}
func main() {
d := dst{}
s := src{ID: 3, Result: "use tag"}
deepcopy.Copy(&d, &s).RegisterTagName("copy").Do()
fmt.Printf("%#v\n", d)
}
package main
import (
"fmt"
"github.com/antlabs/deepcopy"
)
func main() {
i := []int{1, 2, 3, 4, 5, 6}
var o []int
deepcopy.Copy(&o, &i).Do()
fmt.Printf("%#v\n", o)
}
package main
import (
"fmt"
"github.com/antlabs/deepcopy"
)
func main() {
i := map[string]int{
"cat": 100,
"head": 10,
"tr": 3,
"tail": 44,
}
var o map[string]int
deepcopy.Copy(&o, &i).Do()
fmt.Printf("%#v\n", o)
}
从零实现的 deepcopy 相比 json 序列化与反序列化方式拥有更好的性能
goos: linux
goarch: amd64
pkg: github.com/antlabs/deepcopy
Benchmark_MiniCopy-12 243212 4987 ns/op
Benchmark_DeepCopy-12 273775 4781 ns/op
PASS
ok github.com/antlabs/deepcopy 4.496s
1
yuyoung 2020-05-07 09:49:27 +08:00
咋看着提升不是很明显
|
2
tcfenix 2020-05-07 10:00:36 +08:00
试着对比了一下 jsoniter
要不要试着做一下缓存? |
3
tcfenix 2020-05-07 10:02:24 +08:00
Benchmark_MiniCopy
Benchmark_MiniCopy-12 223624 5366 ns/op Benchmark_DeepCopy Benchmark_DeepCopy-12 321472 3703 ns/op Benchmark_jsoniter Benchmark_jsoniter-12 471108 2422 ns/op PASS 图片贴不出来,这样看一下吧 |
4
guonaihong OP @tcfenix jsoniter 里面也用的 reflect API ?晚上我加下缓存优化下。
|
5
tcfenix 2020-05-07 10:11:48 +08:00
@guonaihong
jsoniter 第一次会反射,但是反射出来的结果会缓存 其实这样代码生成的方式也挺不错的,牺牲掉一点维护性也是可以接受的 https://github.com/globusdigital/deep-copy 当然,golang 没有像 BeanCopier 这样的神器的确是比较可惜了... |
6
guonaihong OP @guonaihong 可否把你的 benckmark 代码发下。我优化下,再看下性能。
|
7
guonaihong OP @yuyoung 标准库里面的代码做了缓存,所有第一个版本只领先了 18%-30%。如果用同样的思路优化,领先的会更多。
毕竟序列化,反序列化的方式深度拷贝要两次 reflect 。 |
8
tcfenix 2020-05-07 10:22:07 +08:00
|
9
guonaihong OP @tcfenix 谢了。
|
10
pmispig 2020-05-07 11:01:04 +08:00
go 原生赋值就是深拷贝啊,你这个是标题党吧。
你这个最多算是异构赋值 |
11
guonaihong OP @pmispig slice, map 可以深度拷贝?
|
12
guonaihong OP @pmispig 结构体里面套指针,套 interface{},套 slice,套 map,不可以深度拷贝。
|
13
useben 2020-05-07 11:40:06 +08:00
和 jinzhu/copier 对比下?
|
14
guonaihong OP @useben 好,会压测下,结果到时候通知。
|
15
Kisesy 2020-05-07 12:29:27 +08:00
不支持多重指针, 比如一个 *int 字段往 **int 字段赋值, 就会报错, 如果用 json 包可以处理
这种情况 jinzhu/copier 也不支持, 但 github.com/petersunbag/coven 支持, 而且更快? 希望楼主加入支持后, 再压测一下 |
16
rrfeng 2020-05-07 12:37:47 +08:00
我只有一个疑问:
支持 tag 是不是多余了?我要是能在源结构里加 tag,直接写个 copy 方法不爽快吗?? 我觉得一个完整的工程里很难用到 deepcopy 这种方法,更多的是用别人的数据结构,然后想复制一份出来操作避免侵入原数据,所以 tag 毫无用武之地…… |
17
guonaihong OP @rrfeng hi rrfeng 。不加 tag 可以直接拷贝的。所有 ->“我要是能在源结构里加 tag,直接写个 copy 方法不爽快吗??”,所以,不 tag,不需要写 copy 方法会更更爽快。。。
从 ->"我觉得一个完整的工程里很难用到 deepcopy 这种方法,更多的是用别人的数据结构,然后想复制一份出来操作避免侵入原数据,所以 tag 毫无用武之地……" ,这里说了 if 的情况,所以 else 也是有点用的,比如都是自己的包,刚好要过滤几个字段。。。 |
18
blackboom 2020-05-07 13:47:00 +08:00
链式调用重构一下?不然都是 Do
``` deepcopy.RegisterTagName("copy").Copy(&d, &s) ``` |
19
guonaihong OP @blackboom ok, 我思考下。
|
20
tcfenix 2020-05-07 14:12:27 +08:00
|
21
tcfenix 2020-05-07 14:13:05 +08:00
goos: darwin
goarch: amd64 pkg: deepcopy Benchmark_MiniCopy Benchmark_MiniCopy-12 182653 5688 ns/op Benchmark_DeepCopy Benchmark_DeepCopy-12 313747 3953 ns/op Benchmark_jsoniter Benchmark_jsoniter-12 495062 2476 ns/op Benchmark_copier Benchmark_copier-12 7714009 152 ns/op Benchmark_coven Benchmark_coven-12 7289439 160 ns/op PASS 试了一下刚才看到的两个库,效果非常好 |
22
guonaihong OP @tcfenix 测试错了吧,把代码贴到 V2EX 呢(我现在翻墙有问题),我测试,copier 是比较慢的,这速度有点像空跑。
|
23
guonaihong OP @tcfenix 这是我的 test code,结果表明 copier 连两次序列化 json 的时间都比不过,性能直接垫底。。。https://github.com/antlabs/deepcopy-benchmark
|
24
lewinlan 2020-05-08 01:32:11 +08:00 via Android
个人觉得少用反射包比较好,这会破坏静态类型的可靠性,我感觉官方也是不希望我们用的。
|
25
tcfenix 2020-05-08 14:42:28 +08:00
https://gist.github.com/eltria/c273e38b7b1a528a1fe3e4920cc22215
之前的确是我的测试代码有问题,现在看起来 coven 的方案是最快的,只需要事先 new 一个 converter |
26
guonaihong OP @lewinlan 是的,反射包要少用,老师傅也容易写出 bug 。
|
27
guonaihong OP @useben 和 jinzhu/copier 对比,deepcopy 快。压测结果可看附言 1.
|
28
guonaihong OP @Kisesy 要支持 dst, src 不对称指针拷贝,要有个好的算法解决循环引用的问题(结构体里面有环路),deepcopy 现在用的算法,是记录指针地址。并且因为 deepcopy 是深度拷贝,要取引用 struct 。如果要支持不对称指针,遇到下面的代码就 gg 了,当然现在是没问题的。coven 是指针浅拷贝,有时间不会解引用,所以不要操这份心.
type R struct { R *R } r := R{} r.R = &r |