18

我正在使用go 1.9. 我想将对象的值深度复制到另一个对象中。我尝试使用 encoding/gob 和 encoding/json 来做到这一点。但是gob编码比json编码需要更多的时间。我看到其他一些类似的问题他们建议 gob 编码应该更快。但我看到完全相反的行为。有人可以告诉我我做错了什么吗?还是有比这两种更好更快的深度复制方法?我的对象结构复杂且嵌套。

测试代码:

package main

import (
    "bytes"
    "encoding/gob"
    "encoding/json"
    "log"
    "time"

    "strconv"
)

// Test ...
type Test struct {
    Prop1 int
    Prop2 string
}

// Clone deep-copies a to b
func Clone(a, b interface{}) {

    buff := new(bytes.Buffer)
    enc := gob.NewEncoder(buff)
    dec := gob.NewDecoder(buff)
    enc.Encode(a)
    dec.Decode(b)
}

// DeepCopy deepcopies a to b using json marshaling
func DeepCopy(a, b interface{}) {
    byt, _ := json.Marshal(a)
    json.Unmarshal(byt, b)
}

func main() {
    i := 0
    tClone := time.Duration(0)
    tCopy := time.Duration(0)
    end := 3000
    for {
        if i == end {
            break
        }

        r := Test{Prop1: i, Prop2: strconv.Itoa(i)}
        var rNew Test
        t0 := time.Now()
        Clone(r, &rNew)
        t2 := time.Now().Sub(t0)
        tClone += t2

        r2 := Test{Prop1: i, Prop2: strconv.Itoa(i)}
        var rNew2 Test
        t0 = time.Now()
        DeepCopy(&r2, &rNew2)
        t2 = time.Now().Sub(t0)
        tCopy += t2

        i++
    }
    log.Printf("Total items %+v, Clone avg. %+v, DeepCopy avg. %+v, Total Difference %+v\n", i, tClone/3000, tCopy/3000, (tClone - tCopy))
}

我得到以下输出:

Total items 3000, Clone avg. 30.883µs, DeepCopy avg. 6.747µs, Total Difference 72.409084ms
4

1 回答 1

24

JSON与gob差异

encoding/gob需要传递类型定义:

该实现为流中的每种数据类型编译自定义编解码器,并且在使用单个编码器传输值流时最有效,从而分摊编译成本。

当您“首先”序列化类型的值时,还必须包含/传输类型的定义,因此解码器可以正确解释和解码流:

一连串的gobs是自我描述的。流中的每个数据项之前都有其类型的规范,用一小组预定义类型表示。

此处详细解释了这一点:Efficient Go serialization of struct to disk

因此,虽然在您的情况下,每次都需要创建一个新的 gob 编码器和解码器,但它仍然是“瓶颈”,是让它变慢的部分。从 JSON 格式编码/解码,类型描述不包含在表示中。

为了证明这一点,做这个简单的改变:

type Test struct {
    Prop1 [1000]int
    Prop2 [1000]string
}

我们在这里所做的是将字段的类型设置为数组,将值“相乘”一千次,而类型信息实际上保持不变(数组中的所有元素都具有相同的类型)。像这样创造它们的价值:

r := Test{Prop1: [1000]int{}, Prop2: [1000]string{}}

现在运行你的测试程序,我机器上的输出:

原来的:

2017/10/17 14:55:53 项目总数 3000,克隆平均。33.63µs,DeepCopy 平均。2.326µs , 总差 93.910918ms

修改版:

2017/10/17 14:56:38 项目总数 3000,克隆平均。119.899µs,DeepCopy 平均。462.608µs , 总差 -1.02812648s

如您所见,在原始版本中 JSON 更快,但在修改后的版本中gob变得更快,因为传输类型信息的成本摊销了。

测试/基准测试方法

现在开始你的测试方法。这种衡量性能的方法很糟糕,并且会产生非常不准确的结果。相反,您应该使用 Go 的内置测试和基准测试工具。有关详细信息,请阅读代码顺序和性能

这些克隆的注意事项

这些方法与反射一起工作,因此只能“克隆”可通过反射访问的字段,即:导出。他们也经常不管理指针相等性。我的意思是如果你在一个结构中有 2 个指针字段,都指向同一个对象(指针相等),在编组和解组之后,你将得到 2 个指向 2 个不同值的不同指针。这甚至可能在某些情况下导致问题。它们也不处理自引用结构,它充其量会返回错误,或者在错误的情况下导致无限循环或 goroutine 堆栈超出。

“正确”的克隆方式

考虑到上面提到的警告,通常正确的克隆方法需要“内部”的帮助。也就是说,克隆特定类型通常只有在该类型(或该类型的包)提供此功能时才有可能。

是的,提供“手动”克隆功能并不方便,但另一方面,它将优于上述方法(甚至可能是数量级),并且克隆过程所需的“工作”内存量最少。

于 2017-10-17T14:48:35.403 回答