在Go语言中,内存分配主要有两种方式:
关键区别:
逃逸分析是Go编译器在编译时进行的一种优化技术,它会分析变量的生命周期和使用方式,自动决定将变量分配在栈上还是堆上。
在下面代码中,将x的变量的地址返回出去了,这个时候会将x放到堆上。
func allocate() *int {
x := 42
return &x // x escapes to the heap
}
func main() {
allocate()
}
1
2
3
4
5
6
7
8
通过添加参数-gcflags="-m"可以看到如下逃逸的信息
$ go build -gcflags="-m" main.go
...
./main.go:4:2: moved to heap: x
1
2
3
func escape() *int {
x := 10
return &x // escapes
}
1
2
3
4
func closureEscape() func() int {
x := 5
return func() int { return x } // x escapes
}
1
2
3
4
func toInterface(i int) interface{} {
return i // escapes if type info needed at runtime
}
1
2
3
var global *int
func assignGlobal() {
x := 7
global = &x // escapes
}
1
2
3
4
5
6
func makeLargeSlice() []int {
s := make([]int, 10000) // may escape due to size
return s
}
1
2
3
4
type Data struct {
A, B, C int
}
// 栈分配
func StackAlloc() Data {
return Data{1, 2, 3} // stays on stack
}
// 堆分配
func HeapAlloc() *Data {
return &Data{1, 2, 3} // escapes to heap
}
func BenchmarkStackAlloc(b *testing.B) {
for b.Loop() {
_ = StackAlloc()
}
}
func BenchmarkHeapAlloc(b *testing.B) {
for b.Loop() {
_ = HeapAlloc()
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
通过运行会发现两者区别并不大,而且竟然没有堆的申请。这是因为编译器很聪明,它发现通过HeapAlloc返回的指针没有任何意义,所以也就把它放在了栈上。
$ go test -bench=. -benchmem .
goos: darwin
goarch: arm64
pkg: main/demo
cpu: Apple M4 Pro
BenchmarkStackAlloc-12 1000000000 0.2373 ns/op 0 B/op 0 allocs/op
BenchmarkHeapAlloc-12 1000000000 0.2253 ns/op 0 B/op 0 allocs/op
PASS
ok main/demo 1.176s
1
2
3
4
5
6
7
8
9
我需要强制让它分配到堆上,使用全局赋值,修改代码后如下
type Data struct {
A, B, C int
}
var sink *Data
func HeapAllocEscape() {
d := &Data{1, 2, 3}
sink = d // d escapes to heap
}
func StackAlloc() Data {
return Data{1, 2, 3} // stays on stack
}
func BenchmarkStackAlloc(b *testing.B) {
for range b.N {
_ = StackAlloc()
}
}
func BenchmarkHeapAlloc(b *testing.B) {
for range b.N {
HeapAllocEscape()
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
运行结果如下,使用堆存储的开销:35倍的慢调用,24 字节的分配和 1 次垃圾回收的对象。
$ go test -bench=. -benchmem .
goos: darwin
goarch: arm64
pkg: main/demo
cpu: Apple M4 Pro
BenchmarkStackAlloc-12 1000000000 0.2285 ns/op 0 B/op 0 allocs/op
BenchmarkHeapAlloc-12 147388731 8.117 ns/op 24 B/op 1 allocs/op
PASS
ok main/demo 3.064s
1
2
3
4
5
6
7
8
9
在Go语言中,内存分配主要有两种方式:
关键区别:
逃逸分析是Go编译器在编译时进行的一种优化技术,它会分析变量的生命周期和使用方式,自动决定将变量分配在栈上还是堆上。
在下面代码中,将x的变量的地址返回出去了,这个时候会将x放到堆上。
func allocate() *int {
x := 42
return &x // x escapes to the heap
}
func main() {
allocate()
}
1
2
3
4
5
6
7
8
通过添加参数-gcflags="-m"可以看到如下逃逸的信息
$ go build -gcflags="-m" main.go
...
./main.go:4:2: moved to heap: x
1
2
3
func escape() *int {
x := 10
return &x // escapes
}
1
2
3
4
func closureEscape() func() int {
x := 5
return func() int { return x } // x escapes
}
1
2
3
4
func toInterface(i int) interface{} {
return i // escapes if type info needed at runtime
}
1
2
3
var global *int
func assignGlobal() {
x := 7
global = &x // escapes
}
1
2
3
4
5
6
func makeLargeSlice() []int {
s := make([]int, 10000) // may escape due to size
return s
}
1
2
3
4
type Data struct {
A, B, C int
}
// 栈分配
func StackAlloc() Data {
return Data{1, 2, 3} // stays on stack
}
// 堆分配
func HeapAlloc() *Data {
return &Data{1, 2, 3} // escapes to heap
}
func BenchmarkStackAlloc(b *testing.B) {
for b.Loop() {
_ = StackAlloc()
}
}
func BenchmarkHeapAlloc(b *testing.B) {
for b.Loop() {
_ = HeapAlloc()
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
通过运行会发现两者区别并不大,而且竟然没有堆的申请。这是因为编译器很聪明,它发现通过HeapAlloc返回的指针没有任何意义,所以也就把它放在了栈上。
$ go test -bench=. -benchmem .
goos: darwin
goarch: arm64
pkg: main/demo
cpu: Apple M4 Pro
BenchmarkStackAlloc-12 1000000000 0.2373 ns/op 0 B/op 0 allocs/op
BenchmarkHeapAlloc-12 1000000000 0.2253 ns/op 0 B/op 0 allocs/op
PASS
ok main/demo 1.176s
1
2
3
4
5
6
7
8
9
我需要强制让它分配到堆上,使用全局赋值,修改代码后如下
type Data struct {
A, B, C int
}
var sink *Data
func HeapAllocEscape() {
d := &Data{1, 2, 3}
sink = d // d escapes to heap
}
func StackAlloc() Data {
return Data{1, 2, 3} // stays on stack
}
func BenchmarkStackAlloc(b *testing.B) {
for range b.N {
_ = StackAlloc()
}
}
func BenchmarkHeapAlloc(b *testing.B) {
for range b.N {
HeapAllocEscape()
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
运行结果如下,使用堆存储的开销:35倍的慢调用,24 字节的分配和 1 次垃圾回收的对象。
$ go test -bench=. -benchmem .
goos: darwin
goarch: arm64
pkg: main/demo
cpu: Apple M4 Pro
BenchmarkStackAlloc-12 1000000000 0.2285 ns/op 0 B/op 0 allocs/op
BenchmarkHeapAlloc-12 147388731 8.117 ns/op 24 B/op 1 allocs/op
PASS
ok main/demo 3.064s
1
2
3
4
5
6
7
8
9