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

2020-05-07 09:32:01 +08:00
 guonaihong

出发点

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

项目地址

https://github.com/antlabs/deepcopy

作用

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

feature

内容

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

6201 次点击
所在节点    Go 编程语言
28 条回复
yuyoung
2020-05-07 09:49:27 +08:00
咋看着提升不是很明显
tcfenix
2020-05-07 10:00:36 +08:00
试着对比了一下 jsoniter
要不要试着做一下缓存?
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

图片贴不出来,这样看一下吧
guonaihong
2020-05-07 10:05:26 +08:00
@tcfenix jsoniter 里面也用的 reflect API ?晚上我加下缓存优化下。
tcfenix
2020-05-07 10:11:48 +08:00
@guonaihong
jsoniter 第一次会反射,但是反射出来的结果会缓存

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

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

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

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/669151

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX