Before the speech
Most programmers use technology at a higher level of abstraction, including languages, frameworks, design patterns, etc., for faster development efficiency. As a result, many programmers, myself included, are a little rusty on the basics and the basics, but it’s the underlying stuff that builds the solutions we know and determines the limits of a technician.
Allocating memory dynamically while writing C and C++ is managed manually by the programmer. The advantage of doing this is that you can get a good idea of how much memory you need to allocate, but forgetting to free memory can lead to memory leaks.
Rust also allocates memory differently than 👆. The main feature of Rust’s memory management can be seen as that the compiler inserts deletes in appropriate places to free memory, so that you don’t need to explicitly specify the free memory, and the Runtime doesn’t need any GC, but to do this, The compiler needs to be able to figure out where to delete, which requires your code to follow its rules.
Compared to the above memory management languages, languages like Java and Golang are designed with gc in the Garbage Collection (runtime), so that programmers don’t need to manage their own memory, which really frees up programmer’s hands and allows us to focus on coding.
Function of stack frames
When a function is running, a stack frame needs to be created for it on the stack to record the information generated at run time. Therefore, each function creates a stack frame before execution and destroys the stack frame when it returns.
A register called bp is usually used to hold the starting address of the running function stack frame. Since the stack pointer (SP) always holds the address of the top of the stack, the stack pointer also holds the end address of the running function stack frame.
At the time of destruction, the stack pointer (SP) is moved to the position of the current stack base address (BP). At this point, the stack pointer and the stack base address point to the same location.
Go Memory Escape
This can be thought of simply as memory allocated within a function call, which is returned to the system as the function returns. Here’s an example:
package main
import "fmt"
func main(a) {
f := foo("Ding")
fmt.Println(f)
}
type bar struct {
s string
}
func foo(s string) bar {
f := new(bar) // Will new(bar) escape??
defer func(a) {
f = nil
}()
f.s = s
return *f
}
Copy the code
I think a lot of people think there was an escape, but did it really happen? Go build-gcflags =-m escape/struct.go
Escape /struct.go:7:13: f escapes to heap because of dynamic type escape, FMT.Println(a… Interface {}) can be difficult to determine the exact type of its arguments at compile time, and can result in escapes.
Let’s continue with this example:
package main
import "fmt"
func main(a) {
f := foo("Ding")
fmt.Println(f)
}
type bar struct {
s string
}
func foo(s string) *bar {
f := new(bar) // Will new(bar) escape??
defer func(a) {
f = nil
}()
f.s = s
return f
}
Copy the code
F := new(bar) will escape occur?
$: go build -gcflags=-m escape/struct.go
# command-line-argumentsescape/struct.go:16:8: can inline foo.func1 escape/struct.go:7:13: inlining call to fmt.Println escape/struct.go:14:10: Leaking param: s escape/struct.go:15:10: new(bar) escapes to heap ✅ escape/struct.go:16:8: Leaking param: s escape/struct.go:15:10: New (bar) escapes to heap ✅ escape/struct. func literal does not escape escape/struct.go:7:13: []interface {}{... } does not escape <autogenerated>:1: .this does not escapeCopy the code
Go can return a pointer to a local variable. This is actually a classic case of variable escape, although inside foo() f is a local variable and its value is returned by the function’s return value. F itself is a pointer that points to a memory address that is not a stack but a heap.
Read on for an example:
package main
func main(a) {
Slice() / /??? Will escape happen?
}
func Slice(a) {
s := make([]int.10000.10000)
for index, _ := range s {
s[index] = index
}
}
Copy the code
I think a lot of people would say no, in fact there’s an escape, and the object is actually allocated to the heap when there’s not enough stack space to hold the current object or the current slice length can’t be determined.
One last example:
package main
import (
"fmt"
"io"
"os"
)
func main(a) {
Println(string(*ReverseA("Ding Ding"))) // ???
}
func Println(str string) {
io.WriteString(os.Stdout,
str+"\n")}func ReverseA(str string)* []rune {
result := make([]rune.0.len(str))
for _, v := range []rune(str) {
v := v
defer func(a) {
result = append(result, v)
}()
}
return &result
}
Copy the code
If a variable is addressed, returned by a function that returns a pointer value, and there are closures, the compiler is not sure if you want to expand your slice capacity, and put it on the heap, resulting in escape.
So I optimized the code and took a look
package main
import (
"io"
"os"
)
func main(a) {
result := []rune("Ding Ding")
ReverseB(result)
Println(string(result))
}
func ReverseB(runes []rune) {
for i, j := 0.len(runes)- 1; i < j; i, j = i+1, j- 1 {
runes[i], runes[j] = runes[j], runes[i]
}
}
func Println(str string) {
io.WriteString(os.Stdout,
str+"\n")}Copy the code
How do I know how variables are assigned?
Quote (golang.org) FAQ official says:
To be precise, you don’t need to know that variables in Golang live as long as they are referenced, and that whether they are stored on the heap or stack is up to the internal implementation regardless of the specific syntax. Knowing where variables are stored does matter for efficient programming. If possible, the Golang compiler assigns the local variables of a function to the stack frame. However, if the compiler cannot ensure that the variable is no longer referenced after the function return, the compiler assigns the variables to the heap. Also, if a local variable is very large, it should also be assigned to the heap rather than the stack.
The small knot
- The benefit of escape analysis is to reduce
gc
The pressure of the - Memory allocated on the stack is not required
gc
To deal with - Synchronization elimination, if you define an object that has a synchronization lock on its method, but only one thread is accessing it at runtime, then the machine code after escaping will run without the synchronization lock.