在 golang 中深度复制对象的更快方法,JSON vs gob
Quicker way to deepcopy objects in golang, JSON vs gob
我正在使用 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
JSON vs gob
差异
encoding/gob
包需要传输类型定义:
The implementation compiles a custom codec for each data type in the stream and is most efficient when a single Encoder is used to transmit a stream of values, amortizing the cost of compilation.
当您“首先”序列化一个类型的值时,该类型的 定义 也必须包含/传输,以便解码器可以正确解释和解码流:
A stream of gobs is self-describing. Each data item in the stream is preceded by a specification of its type, expressed in terms of a small set of predefined types.
这里有详细的解释:
因此,虽然在您的情况下每次都需要创建一个新的 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 Total items 3000, Clone avg. 33.63µs, DeepCopy avg. 2.326µs, Total Difference 93.910918ms
修改版本:
2017/10/17 14:56:38 Total items 3000, Clone avg. 119.899µs, DeepCopy avg. 462.608µs, Total Difference -1.02812648s
如您所见,在原始版本中 JSON 更快,但在修改后的版本中 gob
变得更快,因为传输类型信息的成本被分摊了。
测试/基准方法
现在开始你的测试方法。这种衡量性能的方法很糟糕,会产生非常不准确的结果。相反,您应该使用 Go 的内置测试和基准测试工具。详情请阅读.
这些克隆的注意事项
这些方法与反射一起工作,因此只能“克隆”可通过反射访问的字段,即:导出。此外,他们通常不管理指针相等性。我的意思是,如果你在一个结构中有 2 个指针字段,都指向同一个对象(指针相等),在编组和解组之后,你将得到 2 个不同的指针指向 2 个不同的值。在某些情况下,这甚至可能会导致问题。它们也不处理自引用结构,这最多 returns 一个错误,或者在错误的情况下导致无限循环或 goroutine 堆栈超出。
“正确”的克隆方式
考虑到上述注意事项,通常克隆的正确方法需要来自“内部”的帮助。也就是说,克隆特定类型通常只有在该类型(或该类型的包)提供此功能时才有可能。
是的,提供“手动”克隆功能并不方便,但另一方面它将优于上述方法(甚至可能是数量级),并且需要最少的“工作”内存用于克隆过程。
我正在使用 go 1.9
。我想将对象的值深度复制到另一个对象中。我试着用 encoding/gob 和 encoding/json 来做。但是 gob 编码比 json 编码需要更多的时间。我看到其他一些问题,例如
测试代码:
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
JSON vs gob
差异
encoding/gob
包需要传输类型定义:
The implementation compiles a custom codec for each data type in the stream and is most efficient when a single Encoder is used to transmit a stream of values, amortizing the cost of compilation.
当您“首先”序列化一个类型的值时,该类型的 定义 也必须包含/传输,以便解码器可以正确解释和解码流:
A stream of gobs is self-describing. Each data item in the stream is preceded by a specification of its type, expressed in terms of a small set of predefined types.
这里有详细的解释:
因此,虽然在您的情况下每次都需要创建一个新的 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 Total items 3000, Clone avg. 33.63µs, DeepCopy avg. 2.326µs, Total Difference 93.910918ms
修改版本:
2017/10/17 14:56:38 Total items 3000, Clone avg. 119.899µs, DeepCopy avg. 462.608µs, Total Difference -1.02812648s
如您所见,在原始版本中 JSON 更快,但在修改后的版本中 gob
变得更快,因为传输类型信息的成本被分摊了。
测试/基准方法
现在开始你的测试方法。这种衡量性能的方法很糟糕,会产生非常不准确的结果。相反,您应该使用 Go 的内置测试和基准测试工具。详情请阅读
这些克隆的注意事项
这些方法与反射一起工作,因此只能“克隆”可通过反射访问的字段,即:导出。此外,他们通常不管理指针相等性。我的意思是,如果你在一个结构中有 2 个指针字段,都指向同一个对象(指针相等),在编组和解组之后,你将得到 2 个不同的指针指向 2 个不同的值。在某些情况下,这甚至可能会导致问题。它们也不处理自引用结构,这最多 returns 一个错误,或者在错误的情况下导致无限循环或 goroutine 堆栈超出。
“正确”的克隆方式
考虑到上述注意事项,通常克隆的正确方法需要来自“内部”的帮助。也就是说,克隆特定类型通常只有在该类型(或该类型的包)提供此功能时才有可能。
是的,提供“手动”克隆功能并不方便,但另一方面它将优于上述方法(甚至可能是数量级),并且需要最少的“工作”内存用于克隆过程。