Concisely deep copy a slice?

前端 未结 3 882
粉色の甜心
粉色の甜心 2020-12-17 17:11

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 ma

相关标签:
3条回答
  • 2020-12-17 17:45

    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...)
    
    0 讨论(0)
  • 2020-12-17 17:52
    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
    $ 
    
    0 讨论(0)
  • 2020-12-17 17:55

    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.

    0 讨论(0)
提交回复
热议问题