1

I'm wondering if it's possible use sync.Pool with an array or slice? For example, could sync.Pool speed up the following when handling tens-of-thousands of requests per second? The example is simply understanding Go better.

// Handler that uses GenerateArray
func ok(w http.ResponseWriter, r *http.Request) {
    var res [100000]uint64
    fibonacci.GenerateArray(&res)
    fmt.Fprintf(w, "OK")
}

func GenerateArray(data *[100000]uint64) {
    var start uint16 = 1000
    var counter uint32
    for start >= 1 {
        var num = 90
        var n1, n2, temp uint64 = 0, 1, 0

        for num >= 1 {
            temp = n2
            n1, n2 = temp, n1+n2
            data[counter] = n2
            counter++
            num--
        }
        start--
    }
}

EDIT: This is the slice version as suggested by Icza. Hopefully I did it right as I'm learning.

res := make([]uint64, 100000)
fibonacci.GenerateSlice(res)

// PopulateSlice does this...
func PopulateSlice(data []uint64) {
    var start uint16 = 1000
    var counter uint32
    for start >= 1 {
        var num = 90
        var n1, n2, temp uint64 = 0, 1, 0

        for num >= 1 {
            temp = n2
            n1, n2 = temp, n1+n2
            data[counter] = n2
            counter++
            num--
        }
        start--
    }
}

Returning it.

func GenerateSlice() []uint64 {
    data := make([]uint64, 0, 100000)
    var start uint16 = 1000
    var counter uint32
    for start >= 1 {
        var num = 90
        var n1, n2, temp uint64 = 0, 1, 0

        for num >= 1 {
            temp = n2
            n1, n2 = temp, n1+n2
            // data[counter] = n2
            data = append(data, n2)
            counter++
            num--
        }
        start--
    }
    return data
}
Cazineer
  • 2,235
  • 4
  • 26
  • 44
  • A very common thing you'll see is that the use of _arrays_ is discouraged in go, save for a couple of _very_ specific cases, it's best to use slices (ideally with an explicit cap). Passing pointers to slices/arrays is also a bit ofn odd thing to do. Your function is called `GenerateArray`, but instead it's actually _populating_ an array that's already been created. Just create && return – Elias Van Ootegem Aug 16 '19 at 10:04
  • @EliasVanOotegem I forgot to rename it. Originally it generated an array and returned it. I started with a slice but the performance was really bad. I was able to over double the the performance with a pointer to the array. – Cazineer Aug 16 '19 at 10:07
  • Did you just use a slice literal, or did you specify a cap/Len? – Elias Van Ootegem Aug 16 '19 at 10:09
  • `data := make([]uint64, 100000)` cap/len with index and `data := make([]uint64, 0, 100000)` with append – Cazineer Aug 16 '19 at 10:17
  • The first is specifying the length, in C terms, that's equivalent to `uint64 *data = calloc(100000, sizeof *data);` (allocate on heap, and initialise the memory to zeroes. The second allocates a block of memory (optimistically), and each append will initialise a new offset to a given value. This is just broad strokes, but that's essentially what happens. Using the array allocates on stack (if possible), and will have a bit less overhead as a result, but you're passing a pointer, which still introduces a level of indirection. BTW in the last snippet, the `counter` var is redundant – Elias Van Ootegem Aug 16 '19 at 10:41
  • Thanks for the info. I turned my PC back on and did those quick and forgot to remove that var. 4:00am here almost. This whole thing started with near identical Kotlin code handling 42k rs and I wanted to as part of learning Go, have Go surpass Kotlin. The array pointer method clocks in at 49k rs and the rest are 21k-23k. I come from the JS world and Go is quite different. Cheers – Cazineer Aug 16 '19 at 10:47
  • Just a question, though: why are you considering `sync.Pool` for this? a `sync.Pool`'s main purpose is to place some parts of memory you're using outside of the normal GC cycle. It's a useful/poweful tool, but it's not to be used as a silver bullet: https://golang.org/pkg/sync/#Pool – Elias Van Ootegem Aug 16 '19 at 10:49

1 Answers1

5

I'm wondering if it's possible use sync.Pool with an array or slice?

Yes, sync.Pool can be used with any Go values.

A use case, quoting from its doc:

An example of good use of a Pool is in the fmt package, which maintains a dynamically-sized store of temporary output buffers. The store scales under load (when many goroutines are actively printing) and shrinks when quiescent.

However, it your case most likely it would not result in a performance gain. Arrays are "values", so when you pass an array, its value (all its elements) are copied. So if you would put an array into the pool, it would be copied. When you would get an array from your pool, it again would be copied. This is not an improvement, it's a detriment.

Slices on the other hand are "nice" headers, pointing to a backing array. So passing / getting slices means just passing / getting this header. With slices, you may very well experience a performance boost. Benchmark!

See related: Are golang slices passed by value?

icza
  • 389,944
  • 63
  • 907
  • 827
  • Thanks for the answer. I updated the code in my original question to pass a slice. I've tried passing the slice into the function, as well as generating the slice from within the function and returning it. We are probably getting off topic here but generating the slice in the function or pass it in, is 68% less efficient then the array pointer example. My test server only handles 21k r/s with the slice and 49k r/s with the array pointer. – Cazineer Aug 16 '19 at 10:14