简洁地深拷贝一个切片?

huangapple go评论89阅读模式
英文:

Concisely deep copy a slice?

问题

在Go语言中,有一种简洁高效的方法可以深拷贝一个切片。我需要将切片复制到一个新的后备数组中,因为另一个数组是由其他对象拥有的,并且在复制之后可能会被修改。

我目前是这样做的:

copy := append([]T{}, orig...)

其中Torig的元素类型。

英文:

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 &quot;testing&quot;

var result []T

const size = 10000

type T int

func BenchmarkCopy(b *testing.B) {
	orig := make([]T, size)

	for n := 0; n &lt; 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 &lt; 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 &quot;fmt&quot;

func main() {
	type T int
	slice := make([]T, 8)
	for i := range slice {
		slice[i] = T(i)
	}
	fmt.Println(len(slice), cap(slice), &amp;slice[0], slice)
	slicecopy := append([]T(nil), slice...)
	fmt.Println(len(slicecopy), cap(slicecopy), &amp;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(&quot;Copy a slice:&quot;, slice3)

Benchmarks:

package main

import &quot;testing&quot;

var result []T

const size = 1000

type T int

func BenchmarkCopy(b *testing.B) {
	orig := make([]T, size)
	for n := 0; n &lt; 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 &lt; b.N; n++ {
		cpy := append([]T{}, orig...)
		orig = cpy
	}
	result = orig
}

func BenchmarkAppendPreCapped(b *testing.B) {
	orig := make([]T, size)
	for n := 0; n &lt; 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 &lt; 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...)

huangapple
  • 本文由 发表于 2014年11月21日 14:45:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/27055626.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定