英文:
Concisely deep copy a slice?
问题
在Go语言中,有一种简洁高效的方法可以深拷贝一个切片。我需要将切片复制到一个新的后备数组中,因为另一个数组是由其他对象拥有的,并且在复制之后可能会被修改。
我目前是这样做的:
copy := append([]T{}, orig...)
其中T
是orig
的元素类型。
英文:
In Go, what's a concise/well-performing way to deep copy a slice? I need to copy the slice to a new backing array, because the other array is owned by something else and may be modified after the copy.
I'm currently doing it like this:
copy := append([]T{}, orig...)
where T
is the element type of orig
.
答案1
得分: 33
不确定没有基准测试的情况下哪种解决方案是最快的,但另一种选择是使用内置的copy
函数:
cpy := make([]T, len(orig))
copy(cpy, orig)
根据文档:
func copy(dst, src []Type) int
copy内置函数将元素从源切片复制到目标切片。(作为特例,它还可以将字节从字符串复制到字节切片。)源和目标可能重叠。copy返回复制的元素数,这将是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
}
我不确定何时/是否执行零填充。但是如果你查看汇编代码,在append版本中会有以下内容:
CALL ,runtime.growslice(SB)
而在copy版本中会调用:
CALL ,runtime.makeslice(SB)
我猜这两个调用都执行零填充。
英文:
Not sure which solution is fastest without a benchmark, but an alternative is using the built in copy
:
cpy := make([]T, len(orig))
copy(cpy, orig)
From the documentation:
>func copy(dst, src []Type) int
>
> The copy built-in function copies elements from a source slice into a
> destination slice. (As a special case, it also will copy bytes from a
> string to a slice of bytes.) The source and destination may overlap.
> Copy returns the number of elements copied, which will be the minimum
> of len(src) and len(dst).
Note
The solution will copy all the values in the slice. If the slice contains pointers or structs with pointer fields, these pointer values will still point to the same values as the orig
slice.
Benchmark
Benchmarking the two options, you can see they have very similar performance.
BenchmarkCopy 100000 24724 ns/op
BenchmarkAppend 100000 24967 ns/op
ok benchmark 5.478s
This is the benchmark code:
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
}
I am not sure when/if the zero-fill is performed. But if you look at the assembly, in the append version you will have:
CALL ,runtime.growslice(SB)
while the copy will call:
CALL ,runtime.makeslice(SB)
and I would guess that both of these calls performs the zero-fill.
答案2
得分: 6
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'
// 复制一个切片(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
$
英文:
slicecopy := append([]T(nil), slice...)
For example,
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)
}
Output:
<pre>
8 8 0x10322160 [0 1 2 3 4 5 6 7]
8 8 0x103221a0 [0 1 2 3 4 5 6 7]
</pre>
References:
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)
Benchmarks:
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() {}
Output:
$ 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
$
答案3
得分: 3
似乎最快的方法是在具有足够空间的切片中追加元素。我已经根据@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...)
英文:
It would seem the fastest way is to append to a slice with the necessary space. I've extended @Anisus answer with the benchmark results, and the resulting fastest solution.
BenchmarkCopy 100000 18240 ns/op
BenchmarkAppend 100000 18276 ns/op
BenchmarkAppendPreCapped 100000 16407 ns/op
BenchmarkAppendPreCapped is likely avoiding zeroing and/or growing of the slice. It looks like so:
copy := append(make([]T, 0, len(orig)), orig...)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论