在Go中,什么是深度复制切片的简洁/良好表现方式?我需要将切片复制到新的支持数组,因为另一个数组由其他数组拥有,并且可能在复制后被修改。
我目前正在这样做:
copy := append([]T{}, orig...)
其中T
是orig
的元素类型。
答案 0 :(得分:11)
如果没有基准测试,不确定哪种解决方案最快,但另一种选择是使用内置的copy
:
cpy := make([]T, len(orig))
copy(cpy, orig)
func copy(dst,src [] Type)int
复制内置函数将元素从源切片复制到 目的地切片。 (作为一种特殊情况,它也会从a复制字节 字符串到一片字节。)源和目标可能重叠。 复制返回复制的元素数,这将是最小值 len(src)和len(dst)。
注意强>
解决方案将复制切片中的所有值。如果切片包含带有指针字段的指针或结构,则这些指针值仍将指向与orig
切片相同的值。
<强>基准强>
对两个选项进行基准测试,您可以看到它们具有非常相似的性能。
BenchmarkCopy 100000 24724 ns/op
BenchmarkAppend 100000 24967 ns/op
ok benchmark 5.478s
这是基准代码:
package main
import "testing"
var result []T
const size = 10000
type T int
func BenchmarkCopy(b *testing.B) {
orig := make([]T, size)
for n := 0; n < b.N; n++ {
cpy := make([]T, len(orig))
copy(cpy, orig)
orig = cpy
}
result = orig
}
func BenchmarkAppend(b *testing.B) {
orig := make([]T, size)
for n := 0; n < b.N; n++ {
cpy := append([]T{}, orig...)
orig = cpy
}
result = orig
}
我不确定何时/是否执行了零填充。但是如果你看看程序集,在附加版本中你会得到:
CALL ,runtime.growslice(SB)
副本将调用:
CALL ,runtime.makeslice(SB)
我猜这两个调用都会执行零填充。
答案 1 :(得分:1)
似乎最快的方法是附加到具有必要空间的切片。我已经用基准测试结果扩展了@Anisus的答案,以及最快的解决方案。
BenchmarkCopy 100000 18240 ns/op
BenchmarkAppend 100000 18276 ns/op
BenchmarkAppendPreCapped 100000 16407 ns/op
BenchmarkAppendPreCapped可能会避免切片归零和/或增长。它看起来像这样:
copy := append(make([]T, 0, len(orig)), orig...)
答案 2 :(得分:0)
slicecopy := append([]T(nil), slice...)
例如,
package main
import "fmt"
func main() {
type T int
slice := make([]T, 8)
for i := range slice {
slice[i] = T(i)
}
fmt.Println(len(slice), cap(slice), &slice[0], slice)
slicecopy := append([]T(nil), slice...)
fmt.Println(len(slicecopy), cap(slicecopy), &slicecopy[0], slicecopy)
}
输出:
8 8 0x10322160 [0 1 2 3 4 5 6 7] 8 8 0x103221a0 [0 1 2 3 4 5 6 7]
参考文献:
Arrays, slices (and strings): The mechanics of 'append'
// Make a copy of a slice (of int).
slice3 := append([]int(nil), slice...)
fmt.Println("Copy a slice:", slice3)
基准:
package main
import "testing"
var result []T
const size = 1000
type T int
func BenchmarkCopy(b *testing.B) {
orig := make([]T, size)
for n := 0; n < b.N; n++ {
cpy := make([]T, len(orig))
copy(cpy, orig)
orig = cpy
}
result = orig
}
func BenchmarkAppend(b *testing.B) {
orig := make([]T, size)
for n := 0; n < b.N; n++ {
cpy := append([]T{}, orig...)
orig = cpy
}
result = orig
}
func BenchmarkAppendPreCapped(b *testing.B) {
orig := make([]T, size)
for n := 0; n < b.N; n++ {
cpy := append(make([]T, 0, len(orig)), orig...)
orig = cpy
}
result = orig
}
func BenchmarkAppendNil(b *testing.B) {
orig := make([]T, size)
for n := 0; n < b.N; n++ {
cpy := append([]T(nil), orig...)
orig = cpy
}
result = orig
}
func main() {}
输出:
$ go version
go version devel +ffe33f1f1f17 Tue Nov 25 15:41:33 2014 +1100 linux/amd64
$ go test -v -bench=.
testing: warning: no tests to run
PASS
BenchmarkCopy 200000 9983 ns/op
BenchmarkAppend 200000 10004 ns/op
BenchmarkAppendPreCapped 200000 10077 ns/op
BenchmarkAppendNil 200000 9960 ns/op
ok so/test 8.412s
$ go test -v -bench=.
testing: warning: no tests to run
PASS
BenchmarkCopy 200000 10000 ns/op
BenchmarkAppend 200000 10112 ns/op
BenchmarkAppendPreCapped 200000 9892 ns/op
BenchmarkAppendNil 200000 10005 ns/op
ok so/test 8.422s
$ go test -v -bench=.
testing: warning: no tests to run
PASS
BenchmarkCopy 200000 9967 ns/op
BenchmarkAppend 200000 9898 ns/op
BenchmarkAppendPreCapped 200000 10123 ns/op
BenchmarkAppendNil 200000 10022 ns/op
ok so/test 8.424s
$