Search code examples
performancegoparameter-passingcopy-on-write

Performance of function slice parameter vs global variable?


I've got the following function:

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

I'm wondering, since excludedPatterns never changes, should I optimize it by making the var global (and not passing it to the function every time), or does Golang already handle this by passing them as copy-on-write?

Edit: I guess I could pass the slice as a pointer, but I'm still wondering about the copy-on-write behavior (if it exists) and whether, in general, I should worry about passing by value or by pointer.


Solution

  • 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