foreword
This is part 3 of a series on the top 10 most common mistakes in Go: performance issues and memory escapes with Go pointers. The material comes from Teiva Harsanyi , a Go evangelist and now a senior engineer at Docker.
The source code involved in this article is all open source in: The source code of the top ten common errors in Go , you are welcome to pay attention to the public account and get the latest updates of this series in time.
Scenes
We know that function parameters and return values can use variables or pointers to variables.
Go beginners are prone to a misunderstanding:
- It is believed that if the function parameters and return values use the value of the variable, the entire variable will be copied, which is slow
- Think that if the function parameters and return values use pointer types, only the memory address needs to be copied, which is faster
But is this really the case? We can look at this example of this code, and the results of the performance test are as follows:
$ go test -bench .
goos: darwin
goarch: amd64
pkg: pointer
cpu: Intel(R) Core(TM) i5-5250U CPU @ 1.60GHz
BenchmarkByPointer-4 6473781 178.2 ns/op
BenchmarkByValue-4 21760696 47.11 ns/op
PASS
ok pointer 2.894s
It can be seen that the function that uses pointers for both parameters and return values is much slower than the function that uses variable values for both parameters and return values. The time-consuming of the former is 4 times that of the latter.
Beginners may feel a little counter-intuitive when they see this, why is this happening?
This is related to Go's memory management of stack (stack) and heap (heap), and whether variables are allocated on stack or heap will have an impact on performance.
- The efficiency of allocating memory on the stack is higher than that of the heap, and the memory allocated on the stack does not need to be GC, and the memory is automatically reclaimed when it exceeds the scope.
- The memory placed on the heap needs to be reclaimed by the GC, and it is prone to memory fragmentation.
- The compiler decides whether the variable is allocated on the stack or the heap at compile time. Escape analysis is required. The escape analysis is completed at the compile time.
What is escape analysis?
The Go compiler parses the source code and decides which variables are allocated in the stack memory space and which variables are allocated in the heap memory space. This process is called escape analysis, which belongs to an analysis phase of Go code compilation.
Through escape analysis, the compiler will try to allocate objects that can be allocated on the stack on the stack, avoiding the system overhead caused by frequent GC garbage collection of heap memory and affecting program performance.
Case 1
Let's look at the following code: The structure foo
can refer to this example .
func getFooValue() foo {
var result foo
// Do something
return result
}
When the variable result
is defined, the memory space of result
will be allocated on the stack of this goroutine.
When the function returns, if the caller of getFooValue
has received the return value, the value of result
will be copied to the corresponding receiving variable.
The memory space of the variable result
on the stack will be released (marked as unavailable and can no longer be accessed unless this space is allocated to other variables again).
Note : The structure of this case foo
occupies a relatively small memory space, about 0.3KB, and the stack space of the goroutine is sufficient for storage. If foo
occupies too much space, store it in the stack If not, memory will be allocated to the heap.
Case 2
Let's look at the following code:
func getFooPointer() *foo {
var result foo
// Do something
return &result
}
The function getFooPointer
because it returns a pointer, if the variable result
is allocated on the stack, after the function returns, the memory space of result
will be freed. As a result, the variable receiving the return value of the function cannot access the original memory space of result
and becomes a dangling pointer.
So in this case, memory escape will occur, result
will be allocated on the heap, not the stack.
Case 3
Let's look at the following code:
func main() {
p := &foo{}
f(p)
}
The pointer variable p
is the actual parameter of the function f
, because we are calling the function f
in the goroutine where main is located, and there is no cross p
goroutine, so the pointer variable- p
It can be allocated on the stack, it does not need to be allocated on the heap.
Summarize
So how do we know whether the variable is allocated on the stack or the head?
The official statement from Go is:
- From a program correctness point of view, you don't need to care whether the variable is allocated on the stack or the heap. The memory space in which the variable is allocated does not change the semantics of the Go language.
- From the perspective of program performance, you can care about whether the variable is allocated on the stack or the heap, because as mentioned above, the location of the variable storage has an impact on performance.
How do I know whether a variable is allocated on the heap or the stack?
From a correctness standpoint, you don't need to know. Each variable in Go exists as long as there are references to it. The storage location chosen by the implementation is irrelevant to the semantics of the language.
The storage location does have an effect on writing efficient programs. When possible, the Go compilers will allocate variables that are local to a function in that function's stack frame. However, if the compiler cannot prove that the variable is not referenced after the function returns , then the compiler must allocate the variable on the garbage-collected heap to avoid dangling pointer errors. Also, if a local variable is very large, it might make more sense to store it on the heap rather than the stack.
In the current compilers, if a variable has its address taken, that variable is a candidate for allocation on the heap. However, a basic escape analysis recognizes some cases when such variables will not live past the return from the function and can reside on the stack.
In general, escape behavior occurs in the following situations, and the Go compiler will store variables on the heap
- Local variables within a function are referenced outside the function
- Variables of type interface
- Variables with unknown size or dynamic changes, such as slice, map, channel, []byte, etc.
- Local variables whose size is too large, because the stack memory space is relatively small.
In addition, we can also help us with the help of memory escape analysis tools.
Because memory escape analysis is done by the compiler at compile time, you can use the following command to do memory escape analysis:
-
go build -gcflags="-m"
, which can display various optimization results such as escape analysis and inline optimization. -
go build -gcflags="-m -l"
,-l
will disable inline optimization, which can filter out the results of inline optimization and let us focus on the results of escape analysis. -
go build -gcflags="-m -m"
and one more-m
will show more detailed analysis results.
Recommended reading
- Top 10 common mistakes in Go Part 1: Unknown enum value
- Top Ten Common Errors in Go Part 2: The pit of benchmark performance testing
- The syntax mechanism of Go stacks and pointers
- Escape Analysis Principles by ArdanLabs
- Escape Analysis Principles by Gopher Con
open source address
Articles and sample code are open sourced on GitHub: Beginner, Intermediate, and Advanced Tutorials in Go .
Official account: coding advanced. Follow the official account to get the latest Go interview questions and technology stacks.
Personal website: Jincheng's Blog .
Zhihu: Wuji .
Welfare
I have compiled a back-end development learning material package for you, including programming language entry to advanced knowledge (Go, C++, Python), back-end development technology stack, interview questions, etc.
Follow the official account "coding advanced", send a message backend to receive a gift package, this information will be updated from time to time, and add information that I think is valuable. You can also send a message "join the group " to communicate and learn with your peers and answer questions.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。