I stumbled upon an interesting thing while checking performance of memory allocation in GO.
package main
import (
"fmt"
"time"
)
func main(){
const alloc int = 65536
now := time.Now()
loop := 50000
for i := 0; i<loop;i++{
sl := make([]byte, alloc)
i += len(sl) * 0
}
elpased := time.Since(now)
fmt.Printf("took %s to allocate %d bytes %d times", elpased, alloc, loop)
}
I am running this on a Core-i7 2600 with go version 1.6 64bit (also same results on 32bit) and 16GB of RAM (on WINDOWS 10) so when alloc is 65536 (exactly 64K) it runs for 30 seconds (!!!!). When alloc is 65535 it takes ~200ms. Can someone explain this to me please? I tried the same code at home with my core i7-920 @ 3.8GHZ but it didn't show same results (both took around 200ms). Anyone has an idea what's going on?
The performance of arrays is better than slices when accessing a single element. The length of the array is part of the type. It has a certain meaning in a specific scenario.
Only the values stored in the memory managed by the stack are changed. This makes the process of storing and retrieving data from the stack very fast since there is no lookup required. We can just store and retrieve data from the top most block on it. Any data that is stored on the stack has to be finite and static.
In Go dynamic memory block is allocated mainly using new and make. New allocates exact one memory block that is used to create struct type value, whereas, make creates more than one memory block and returns the reference, like a slice, map or channel value.
Slices in Go and Golang The basic difference between a slice and an array is that a slice is a reference to a contiguous segment of an array. Unlike an array, which is a value-type, slice is a reference type. A slice can be a complete array or a part of an array, indicated by the start and end index.
Setting GOGC=off improved performance (down to less than 100ms). Why?
becaue of escape analysis. When you build with go build -gcflags -m
the compiler prints whatever allocations escapes to heap. It really depends on your machine and GO compiler version but when the compiler decides that the allocation should move to heap it means 2 things:
1. the allocation will take longer (since "allocating" on the stack is just 1 cpu instruction)
2. the GC will have to clean up that memory later - costing more CPU time
for my machine, the allocation of 65536 bytes escapes to heap and 65535 doesn't.
that's why 1 bytes changed the whole proccess from 200ms to 30s. Amazing..
Note/Update 2021: as Tapir Liui notes in Go101 with this tweet:
As of Go 1.17, Go runtime will allocate the elements of slice
x
on stack if the compiler proves they are only used in the current goroutine andN <= 64KB
:var x = make([]byte, N)
And Go runtime will allocate the array
y
on stack if the compiler proves it is only used in the current goroutine andN <= 10MB
:var y [N]byte
Then how to allocated (the elements of) a slice which size is larger than 64KB but not larger than 10MB on stack (and the slice is only used in one goroutine)?
Just use the following way:
var y [N]byte var x = y[:]
Considering stack allocation is faster than heap allocation, that would have a direct effect on your test, for alloc
equals to 65536 and more.
Tapir adds:
In fact, we could allocate slices with arbitrary sum element sizes on stack.
const N = 500 * 1024 * 1024 // 500M var v byte = 123 func createSlice() byte { var s = []byte{N: 0} for i := range s { s[i] = v } return s[v] }
Changing 500 to 512 make program crash.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With