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.

2

3 Answers

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.

4
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:

 8 8 0x10322160 [0 1 2 3 4 5 6 7] 8 8 0x103221a0 [0 1 2 3 4 5 6 7] 

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 $ 

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...) 
1

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy