Performance of function slice parameter vs global variable?
Asked Answered
N

3

16

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.

Nowhere answered 30/11, 2015 at 9:49 Comment(5)
blog.golang.org/go-slices-usage-and-internalsHomeless
You can also read this #1863960Gregarious
1. There is no copy on write in Go. 2. Everything is passes by a copy in Go always and ever. 3. Some types (e.g. slices and maps) contain hidden pointers so they seem to be passed by reference, but they are not. 4. If passing excludePattern really is the performance bottleneck in your code I'll pay you a beer. 5. Passing a slice by pointer is completely pointless (given the small size of a slice) unless you want to modify it from inside your function.Heft
Copy-on-write is a concept in reference delivery used languages such as PHP. In the world of pointers, there are no such words.Kiddush
related: Are slices passed by valueSidhu
F
21

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
Faustofaustus answered 30/11, 2015 at 9:57 Comment(0)
M
2

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
Mongoose answered 15/11, 2020 at 4:2 Comment(0)
C
2

I rewrote the benchmark so you can compare the results.

As you can see the ParameterPointer bench start to get ahead after 10 records. Which is very interesting.

package slices_bench

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 paramPointer(s string, ss *[]string) {
    for i := 0; i < 100; i++ { // Cycle to access slice may times
        _ = s
        _ = ss // Access parameter-slice
    }
}

func BenchmarkPerformance(b *testing.B){
    fixture := []struct {
        desc    string
        records int
    }{
        {
            desc:    "1 record",
            records: 1,
        },
        {
            desc:    "10 records",
            records: 10,
        },
        {
            desc:    "100 records",
            records: 100,
        },
        {
            desc:    "1000 records",
            records: 1000,
        },
        {
            desc:    "10000 records",
            records: 10000,
        },
        {
            desc:    "100000 records",
            records: 100000,
        },
    }

    tests := []struct {
        desc string
        fn   func(b *testing.B, n int)
    }{
        {
            desc: "ParameterPointer",
            fn: func(b *testing.B, n int) {
                for j := 0; j < n; j++ {
                    paramPointer("hi", &gslice)
                }
            },
        },
        {
            desc: "Parameter",
            fn: func(b *testing.B, n int) {
                for j := 0; j < n; j++ {
                    param("hi", gslice)
                }
            },
        },
        {
            desc: "Global",
            fn: func(b *testing.B, n int) {
                for j := 0; j < n; j++ {
                    global("hi")
                }
            },
        },
    }

    for _, t := range tests {
        b.Run(t.desc, func(b *testing.B) {
            for _, f := range fixture {
                b.Run(f.desc, func(b *testing.B) {
                    b.ReportAllocs()
                    b.ResetTimer()
                    for i := 0; i < b.N; i++ {
                        t.fn(b, f.records)
                    }
                })
            }
        })
    }
}

Results:

goos: windows
goarch: amd64
pkg: benchs/slices-bench
cpu: Intel(R) Core(TM) i7-10700 CPU @ 2.90GHz
BenchmarkPerformance
BenchmarkPerformance/ParameterPointer
BenchmarkPerformance/ParameterPointer/1_record
BenchmarkPerformance/ParameterPointer/1_record-16           38661910            31.18 ns/op        0 B/op          0 allocs/op
BenchmarkPerformance/ParameterPointer/10_records
BenchmarkPerformance/ParameterPointer/10_records-16          4160023           288.4 ns/op         0 B/op          0 allocs/op
BenchmarkPerformance/ParameterPointer/100_records
BenchmarkPerformance/ParameterPointer/100_records-16          445131          2748 ns/op           0 B/op          0 allocs/op
BenchmarkPerformance/ParameterPointer/1000_records
BenchmarkPerformance/ParameterPointer/1000_records-16          43876         27380 ns/op           0 B/op          0 allocs/op
BenchmarkPerformance/ParameterPointer/10000_records
BenchmarkPerformance/ParameterPointer/10000_records-16          4441        273922 ns/op           0 B/op          0 allocs/op
BenchmarkPerformance/ParameterPointer/100000_records
BenchmarkPerformance/ParameterPointer/100000_records-16          439       2739282 ns/op           0 B/op          0 allocs/op
BenchmarkPerformance/Parameter
BenchmarkPerformance/Parameter/1_record
BenchmarkPerformance/Parameter/1_record-16                  39860619            30.79 ns/op        0 B/op          0 allocs/op
BenchmarkPerformance/Parameter/10_records
BenchmarkPerformance/Parameter/10_records-16                 4152728           288.6 ns/op         0 B/op          0 allocs/op
BenchmarkPerformance/Parameter/100_records
BenchmarkPerformance/Parameter/100_records-16                 445634          2757 ns/op           0 B/op          0 allocs/op
BenchmarkPerformance/Parameter/1000_records
BenchmarkPerformance/Parameter/1000_records-16                 43618         27496 ns/op           0 B/op          0 allocs/op
BenchmarkPerformance/Parameter/10000_records
BenchmarkPerformance/Parameter/10000_records-16                 4450        273960 ns/op           0 B/op          0 allocs/op
BenchmarkPerformance/Parameter/100000_records
BenchmarkPerformance/Parameter/100000_records-16                 435       2739053 ns/op           0 B/op          0 allocs/op
BenchmarkPerformance/Global
BenchmarkPerformance/Global/1_record
BenchmarkPerformance/Global/1_record-16                     38813095            30.97 ns/op        0 B/op          0 allocs/op
BenchmarkPerformance/Global/10_records
BenchmarkPerformance/Global/10_records-16                    4148433           288.4 ns/op         0 B/op          0 allocs/op
BenchmarkPerformance/Global/100_records
BenchmarkPerformance/Global/100_records-16                    429274          2758 ns/op           0 B/op          0 allocs/op
BenchmarkPerformance/Global/1000_records
BenchmarkPerformance/Global/1000_records-16                    43591         27412 ns/op           0 B/op          0 allocs/op
BenchmarkPerformance/Global/10000_records
BenchmarkPerformance/Global/10000_records-16                    4521        274420 ns/op           0 B/op          0 allocs/op
BenchmarkPerformance/Global/100000_records
BenchmarkPerformance/Global/100000_records-16                    436       2751042 ns/op           0 B/op          0 allocs/op
Carboxylase answered 1/9, 2021 at 7:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.