在Go中,深度複製切片的簡潔/良好表現方式是什麼?我需要將切片複製到新的後備數組中,因爲另一個數組擁有其他數據,並且可能在複製後進行修改。簡潔地複製一個切片?
目前,我正在做這樣的:
copy := append([]T{}, orig...)
其中T
是orig
元素類型。
在Go中,深度複製切片的簡潔/良好表現方式是什麼?我需要將切片複製到新的後備數組中,因爲另一個數組擁有其他數據,並且可能在複製後進行修改。簡潔地複製一個切片?
目前,我正在做這樣的:
copy := append([]T{}, orig...)
其中T
是orig
元素類型。
看起來最快的方法是追加切片與必要的空間。我已經擴展了@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...)
我無法重現您的基準測試結果。請發佈您的基準代碼。 – peterSO 2014-11-30 01:01:16
@peterSO:https://gist.github.com/anacrolix/e8baa9c70bbb2b909b19 – 2014-12-08 17:08:20
不確定哪溶液是最快而不基準,但一種替代方案是使用內置的copy
:
cpy := make([]T, len(orig))
copy(cpy, orig)
FUNC拷貝(DST,SRC []類型) int
複製內置函數將源片段中的元素複製到 目標片段中。 (作爲特殊情況,它也會將 字符串中的字節複製到一段字節中。)源和目標可能會重疊。 複製返回複製的元素數量,這將是len(src)和len(dst)的最小值 。
注意
該解決方案將所有的值複製切片。如果切片包含帶指針字段的指針或結構,則這些指針值仍將指向與orig
切片相同的值。
基準
標杆兩個選項,你可以看到他們有很相似的性能。
BenchmarkCopy 100000 24724 ns/op
BenchmarkAppend 100000 24967 ns/op
ok benchmark 5.478s
這是基準代碼:
包主要
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)
,我猜想,這兩個調用執行填零。
我感覺這將零cpy,然後複製它。我希望通過創建一個帶有必要上限的分片並將其添加到它來避免零分。 – 2014-11-22 05:12:14
@MattJoiner:我做了一個基準,看看這兩個表現是否有所不同,但似乎不是。我用基準編輯了答案。 – ANisus 2014-11-22 10:47:52
怎麼了所有的切片分配?做得好。我會自己運行這個基準。 – 2014-11-22 18:10:02
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 1ns/op
BenchmarkAppendNil 200000 10022 ns/op
ok so/test 8.424s
$
不能以一般的方式來完成。使用遞歸類型和遞歸數據結構和指針的語言對深拷貝很困難。什麼是一個指針,一個切片,一個指針切片,一個指向切片的指針的深層副本?現在撒上reflect.Value的... – Volker 2014-11-21 07:49:44
也許我濫用術語深層複製,但我確實提到我想複製支持陣列的存儲。這就夠了。 – 2014-11-22 05:10:44