向繁琐的赋值代码说不。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

6074 次点击
所在节点    Go 编程语言
28 条回复
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

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

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

r := R{}
r.R = &r

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

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

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

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

© 2021 V2EX