向繁琐的赋值代码说不。deepcopy.Copy 深度拷贝来了 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
guonaihong

向繁琐的赋值代码说不。deepcopy.Copy 深度拷贝来了

  •  
  •   guonaihong
    guonaihong 2020 年 5 月 7 日 6705 次点击
    这是一个创建于 2179 天前的主题,其中的信息可能已经有所发展或是发生改变。

    出发点

    一次帮同事 review 代码,想在 go 里面找一个支持深度 Copy 的库,github 上少得可怜。最后找到 json marshal 加 unmarshal 的方式,但是这种方式有两个缺点,第 1 marshal 一次 reflect,unmarshal 一次 reflect,有两次 reflect 的过程,效率会垫底。第 2,不支持过滤条件,这点硬伤,改不了。特对这两点问题,所以想撸个改进版本(更快,更可控)。

    项目地址

    https://github.com/antlabs/deepcopy

    作用

    Go codecov

    deepcopy.Copy 主要用于两个类型间的深度拷贝[从零实现]

    feature

    • 支持异构结构体拷贝, dst 和 src 可以是不同的类型,会拷贝 dst 和 src 交集的部分
    • 多类型支持 struct/map/slice/array/int...int64/uint...uint64/ 等等
    • 性能相比 json 序列化和反序列化的做法,拥有更快的执行速度
    • 可以控制拷贝结构体层次
    • 可以通过 tag 控制感兴趣的字段

    内容

    Installation

    go get github.com/antlabs/deepcopy 

    Quick start

    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) } 

    max copy depth

    如果 src 的结构体嵌套了两套,MaxDepth 可以控制只拷贝一层

    deepcopy.Copy(&dst{}, &src{}).MaxDepth(1).Do() 

    copy only the specified tag

    只拷贝结构体里面有 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) } 

    copy slice

    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) } 

    copy map

    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 条附言    2020 年 5 月 17 日
    压测结果。
    https://github.com/antlabs/deepcopy-benchmark

    deepcopy 下个版本会使用 ptr 方式重新优化一个版本。
    28 条回复    2020-05-17 16:35:22 +08:00
    yuyoung
        1
    yuyoung  
       2020 年 5 月 7 日
    咋看着提升不是很明显
    tcfenix
        2
    tcfenix  
       2020 年 5 月 7 日
    试着对比了一下 jsoniter
    要不要试着做一下缓存?
    tcfenix
        3
    tcfenix  
       2020 年 5 月 7 日
    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

    图片贴不出来,这样看一下吧
    guonaihong
        4
    guonaihong  
    OP
       2020 年 5 月 7 日
    @tcfenix jsoniter 里面也用的 reflect API ?晚上我加下缓存优化下。
    tcfenix
        5
    tcfenix  
       2020 年 5 月 7 日
    @guonaihong
    jsoniter 第一次会反射,但是反射出来的结果会缓存

    其实这样代码生成的方式也挺不错的,牺牲掉一点维护性也是可以接受的
    https://github.com/globusdigital/deep-copy

    当然,golang 没有像 BeanCopier 这样的神器的确是比较可惜了...
    guonaihong
        6
    guonaihong  
    OP
       2020 年 5 月 7 日
    @guonaihong 可否把你的 benckmark 代码发下。我优化下,再看下性能。
    guonaihong
        7
    guonaihong  
    OP
       2020 年 5 月 7 日
    @yuyoung 标准库里面的代码做了缓存,所有第一个版本只领先了 18%-30%。如果用同样的思路优化,领先的会更多。
    毕竟序列化,反序列化的方式深度拷贝要两次 reflect 。
    guonaihong
        9
    guonaihong  
    OP
       2020 年 5 月 7 日
    @tcfenix 谢了。
    pmispig
        10
    pmispig  
       2020 年 5 月 7 日
    go 原生赋值就是深拷贝啊,你这个是标题党吧。
    你这个最多算是异构赋值
    guonaihong
        11
    guonaihong  
    OP
       2020 年 5 月 7 日
    @pmispig slice, map 可以深度拷贝?
    guonaihong
        12
    guonaihong  
    OP
       2020 年 5 月 7 日
    @pmispig 结构体里面套指针,套 interface{},套 slice,套 map,不可以深度拷贝。
    useben
        13
    useben  
       2020 年 5 月 7 日
    和 jinzhu/copier 对比下?
    guonaihong
        14
    guonaihong  
    OP
       2020 年 5 月 7 日
    @useben 好,会压测下,结果到时候通知。
    Kisesy
        15
    Kisesy  
       2020 年 5 月 7 日
    不支持多重指针, 比如一个 *int 字段往 **int 字段赋值, 就会报错, 如果用 json 包可以处理
    这种情况 jinzhu/copier 也不支持, 但 github.com/petersunbag/coven 支持, 而且更快? 希望楼主加入支持后, 再压测一下
    rrfeng
        16
    rrfeng  
       2020 年 5 月 7 日
    我只有一个疑问:
    支持 tag 是不是多余了?我要是能在源结构里加 tag,直接写个 copy 方法不爽快吗??
    我觉得一个完整的工程里很难用到 deepcopy 这种方法,更多的是用别人的数据结构,然后想复制一份出来操作避免侵入原数据,所以 tag 毫无用武之地……
    guonaihong
        17
    guonaihong  
    OP
       2020 年 5 月 7 日
    @rrfeng hi rrfeng 。不加 tag 可以直接拷贝的。所有 ->“我要是能在源结构里加 tag,直接写个 copy 方法不爽快吗??”,所以,不 tag,不需要写 copy 方法会更更爽快。。。

    从 ->"我觉得一个完整的工程里很难用到 deepcopy 这种方法,更多的是用别人的数据结构,然后想复制一份出来操作避免侵入原数据,所以 tag 毫无用武之地……" ,这里说了 if 的情况,所以 else 也是有点用的,比如都是自己的包,刚好要过滤几个字段。。。
    RRL
        18
    RRL  
       2020 年 5 月 7 日
    链式调用重构一下?不然都是 Do
    ```
    deepcopy.RegisterTagName("copy").Copy(&d, &s)
    ```
    guonaihong
        19
    guonaihong  
    OP
       2020 年 5 月 7 日
    @blackboom ok, 我思考下。
    tcfenix
        21
    tcfenix  
       2020 年 5 月 7 日
    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

    试了一下刚才看到的两个库,效果非常好
    guonaihong
        22
    guonaihong  
    OP
       2020 年 5 月 7 日
    @tcfenix 测试错了吧,把代码贴到 V2EX 呢(我现在翻墙有问题),我测试,copier 是比较慢的,这速度有点像空跑。
    guonaihong
        23
    guonaihong  
    OP
       2020 年 5 月 7 日
    @tcfenix 这是我的 test code,结果表明 copier 连两次序列化 json 的时间都比不过,性能直接垫底。。。https://github.com/antlabs/deepcopy-benchmark
    lewinlan
        24
    lewinlan  
       2020 年 5 月 8 日 via Android
    个人觉得少用反射包比较好,这会破坏静态类型的可靠性,我感觉官方也是不希望我们用的。
    tcfenix
        25
    tcfenix  
       2020 年 5 月 8 日
    https://gist.github.com/eltria/c273e38b7b1a528a1fe3e4920cc22215

    之前的确是我的测试代码有问题,现在看起来 coven 的方案是最快的,只需要事先 new 一个 converter
    guonaihong
        26
    guonaihong  
    OP
       2020 年 5 月 9 日
    @lewinlan 是的,反射包要少用,老师傅也容易写出 bug 。
    guonaihong
        27
    guonaihong  
    OP
       2020 年 5 月 17 日
    @useben 和 jinzhu/copier 对比,deepcopy 快。压测结果可看附言 1.
    guonaihong
        28
    guonaihong  
    OP
       2020 年 5 月 17 日
    @Kisesy 要支持 dst, src 不对称指针拷贝,要有个好的算法解决循环引用的问题(结构体里面有环路),deepcopy 现在用的算法,是记录指针地址。并且因为 deepcopy 是深度拷贝,要取引用 struct 。如果要支持不对称指针,遇到下面的代码就 gg 了,当然现在是没问题的。coven 是指针浅拷贝,有时间不会解引用,所以不要操这份心.
    type R struct {
    R *R
    }

    r := R{}
    r.R = &r
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     829 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 51ms UTC 21:18 PVG 05:18 LAX 14:18 JFK 17:18
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86