Are Golang function parameter passed as copy-on-write?

后端 未结 2 948
迷失自我
迷失自我 2020-12-06 10:26

I\'ve got the following function:

func checkFiles(path string, excludedPatterns []string) {
    // ...
}

I\'m wondering, since exclud

相关标签:
2条回答
  • 2020-12-06 11:03

    Piggybacking on @icza's excellent answer, there is another way to pass a slice as parameter: a pointer to a slice.

    When you need to modify the underlying slice, the global variable slice works, but passing it as a parameter does not work, you are effectively working with a copy. To mitigate that, one can actually pass the slice as a pointer.

    Interesting enough, it's actually faster than accessing a global variable:

    package main
    
    import (
        "testing"
    )
    
    var gslice = make([]string, 1000000)
    
    func global(s string) {
        for i := 0; i < 100; i++ { // Cycle to access slice may times
            _ = s
            _ = gslice // Access global-slice
        }
    }
    
    func param(s string, ss []string) {
        for i := 0; i < 100; i++ { // Cycle to access slice may times
            _ = s
            _ = ss // Access parameter-slice
        }
    }
    
    func paramPointer(s string, ss *[]string) {
        for i := 0; i < 100; i++ { // Cycle to access slice may times
            _ = s
            _ = ss // Access parameter-slice
        }
    }
    
    func BenchmarkParameter(b *testing.B) {
        for i := 0; i < b.N; i++ {
            param("hi", gslice)
        }
    }
    
    func BenchmarkParameterPointer(b *testing.B) {
        for i := 0; i < b.N; i++ {
            paramPointer("hi", &gslice)
        }
    }
    
    func BenchmarkGlobal(b *testing.B) {
        for i := 0; i < b.N; i++ {
            global("hi")
        }
    }
    

    Results:

    goos: darwin
    goarch: amd64
    pkg: untitled
    BenchmarkParameter-8            24437403                48.2 ns/op
    BenchmarkParameterPointer-8     27483115                40.3 ns/op
    BenchmarkGlobal-8               25631470                46.0 ns/op
    
    0 讨论(0)
  • 2020-12-06 11:09

    Judging from the name of your function, performance can't be that critical to even consider moving parameters to global variables just to save time/space required to pass them as parameters (IO operations like checking files are much-much slower than calling functions and passing values to them).

    Slices in Go are just small descriptors, something like a struct with a pointer to a backing array and 2 ints, a length and capacity. No matter how big the backing array is, passing slices are always efficient and you shouldn't even consider passing a pointer to them, unless you want to modify the slice header of course.

    Parameters in Go are always passed by value, and a copy of the value being passed is made. If you pass a pointer, then the pointer value will be copied and passed. When a slice is passed, the slice value (which is a small descriptor) will be copied and passed - which will point to the same backing array (which will not be copied).

    Also if you need to access the slice multiple times in the function, a parameter is usually an extra gain as compilers can make further optimization / caching, while if it is a global variable, more care has to be taken.

    More about slices and their internals: Go Slices: usage and internals

    And if you want exact numbers on performance, benchmark!

    Here comes a little benchmarking code which shows no difference between the 2 solutions (passing slice as argument or accessing a global slice). Save it into a file like slices_test.go and run it with go test -bench .

    package main
    
    import (
        "testing"
    )
    
    var gslice = make([]string, 1000)
    
    func global(s string) {
        for i := 0; i < 100; i++ { // Cycle to access slice may times
            _ = s
            _ = gslice // Access global-slice
        }
    }
    
    func param(s string, ss []string) {
        for i := 0; i < 100; i++ { // Cycle to access slice may times
            _ = s
            _ = ss // Access parameter-slice
        }
    }
    
    func BenchmarkParameter(b *testing.B) {
        for i := 0; i < b.N; i++ {
            param("hi", gslice)
        }
    }
    
    func BenchmarkGlobal(b *testing.B) {
        for i := 0; i < b.N; i++ {
            global("hi")
        }
    }
    

    Example output:

    testing: warning: no tests to run
    PASS
    BenchmarkParameter-2    30000000                55.4 ns/op
    BenchmarkGlobal-2       30000000                55.1 ns/op
    ok      _/V_/workspace/IczaGo/src/play  3.569s
    
    0 讨论(0)
提交回复
热议问题