All computing environments must deal with memory management. This article discusses some memory management concepts used by the Go programming language. This article is written for programmers familiar with basic memory management concepts but unfamiliar with Go memory management in particular.
For the purposes of this article, there are three ways to allocate memory: the stack, the heap, and fixed size segments.
The stack has a top that moves up and down. Space is allocated on the stack by moving the top up (i.e. pushing items on the stack) and space is deallocated by moving the top down (i.e. popping items off the stack). The top is an address that can be incremented and decremented with fast arithmetic operations.
Typically, a functions parameters and local variables are allocated on the stack.
Each goroutine has its own stack; thus, no synchronization (e.g., locking) is necessary.
Goroutine stacks are allocated on the heap. If the stack needs to grow beyond the amount allocated for it, then heap operations (allocate new, copy old to new, free old) will occur.
Unlike the stack, the heap does not have a single partition of allocated and free regions. Rather, there is a set of of free regions. A data structure must be used to implement this set of free regions. When an item is allocated, it is removed from the free regions. When an item is freed, it is added back to the set of free regions.
Unlike the stack, the heap is not owned by one goroutine, so manipulating the set of free regions in the heap requires synchronization (e.g., locking).
Memory can also be allocated in one of the fixed sized segments, such as the data segment and code segment. Fixed sized segments are defined at compile time and do not change size at runtime. Read-write fixed size segments (e.g., the data segment) contain global variables while read-only segments (e.g., code segment and rodata segment) contain constant values and instructions.[1]
The Go Programming Language Specification does not define
where items will be allocated. For example, a variable defined as var x int
could be allocated
on the stack or the heap and still follow the language spec. Likewise, the integer pointed
to by p in p := new(int)
could be allocated on the stack or the heap.
However, certain requirements will exclude some choices of memory in certain conditions. For instance:
Escape analysis is used to determine whether an item can be allocated on the stack. It determines if an item created in a function (e.g., a local variable) can escape out of that function or to other goroutines. For example, in the following function, x escapes from the function that defines it:
package escapeanalysis
func Foo() *int {
var x int
return &x
}
Items that escape must be allocated on the heap. Thus x would be allocated on the heap.[2]
The exact escape analysis algorithm can change between Go versions. However, you can
use go tool compile -m
to print optimization decisions, which include the escape analysis. For example, on the previous
program with Go version 1.5.2, you get the following output:
escape.go:3: can inline Foo
escape.go:4: moved to heap: x
escape.go:5: &x escapes to heap
Go uses garbage collection for memory management. The Go garbage collector occasionally has to stop the world to complete the collection task. Since Go version 1.5, the collector is designed so that the stop the world task will take no more than 10 milliseconds out of every 50 milliseconds of execution time.
The garbage collector has to be aware of both heap and stack allocated items. This is easy to see if you consider a heap allocated item, H, referenced by a stack allocated item, S. Clearly, the garbage collector cannot free H until S is freed and so the garbage collector must be aware of lifetime of S, the stack allocated item.
If your process is CPU bound, use runtime/pprof package and go tool pprof
to profile your program. If you see symbols like growslice and newobject taking up a lot of time, optimizing memory allocations may improve performance.
Assuming you've determined optimizing memory use would improve performance of your program, then reduce the number of allocations -- especially heap allocations.
go tool compile -m
to help you identify escaped variables that will be heap allocated and then rewrite your code so that they can be stack allocated.Last updated January 23, 2016