我正在使用go 1.9。我想将对象的值复制到另一个对象中。我尝试用encoding / gob和encoding / json做到这一点。但是gob编码比json编码花费更多的时间。我看像其他一些问题,这和他们建议采空区编码应该是更快的。但我看到完全相反的行为。有人可以告诉我我做错了什么吗?还是有比这两个更好,更快的方法来进行深度复制?我的对象的结构是复杂且嵌套的。
go 1.9
测试代码:
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
gob
该encoding/gob包需要发射类型定义:
encoding/gob
该实现为流中的每种数据类型编译一个自定义编解码器,当使用单个Encoder传输值流时,效率最高,这将分摊编译成本。
当您“首先”序列化类型的值时,还必须包括/传输类型的 定义 ,因此解码器可以正确地解释和解码流:
一滴滴水是自我描述的。流中的每个数据项之前都有其类型的规范,以一小套预定义类型表示。
因此,在您的情况下,有必要每次都创建一个新的gob编码器和解码器,但这仍然是“瓶颈”,这是使其变慢的部分。从JSON格式编码/从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更快,但在修改版本中,JSON gob变得更快,因为传输类型信息的成本摊销了。
现在介绍您的测试方法。这种测量性能的方法很差,并且可能会产生非常不准确的结果。相反,您应该使用Go的内置测试和基准测试工具。
这些方法与反射一起使用,因此只能“克隆”可通过反射访问的字段,即:导出。同样,它们通常不管理指针相等。我的意思是,如果在结构中有2个指针字段,它们都指向同一个对象(指针相等),则在编组和解编组之后,您将获得2个指向2个不同值的不同指针。在某些情况下,这甚至可能导致问题。
考虑到上述注意事项,通常正确的克隆方法需要“内部”的帮助。也就是说,通常只有在特定类型(或该类型的包)提供此功能的情况下,才可能克隆该特定类型。
是的,提供“手动”克隆功能并不方便,但另一方面,它的性能将优于上述方法(甚至可能高出几个数量级),并且需要克隆过程所需的最少“工作”内存。