优化内存逃逸
- 减少不必要的堆分配
- 策略:尽量将对象分配在栈上而不是堆上。Go语言编译器会自动决定对象分配在栈还是堆上,如果对象在函数返回后不再被引用,通常会分配在栈上。
- 示例:
package main
import (
"fmt"
)
// 优化前,s1分配在堆上
func badAlloc() *string {
s := "hello"
return &s
}
// 优化后,s2分配在栈上
func goodAlloc() string {
s := "hello"
return s
}
- 实施步骤:检查函数中返回指针的情况,若返回值在函数外不会被修改且不会被其他长时间存活的对象引用,可以直接返回值而不是指针。
- 使用对象池
- 策略:对于频繁创建和销毁的对象,使用对象池(
sync.Pool
)可以减少内存分配和垃圾回收压力。对象池可以复用已经创建的对象,避免每次都进行内存分配。
- 示例:
package main
import (
"fmt"
"sync"
)
type MyStruct struct {
Data [1024]byte
}
var myPool = sync.Pool{
New: func() interface{} {
return &MyStruct{}
},
}
func usePool() *MyStruct {
obj := myPool.Get().(*MyStruct)
// 使用obj
myPool.Put(obj)
return obj
}
- 实施步骤:确定频繁创建的对象类型,创建对应的
sync.Pool
,在需要使用对象时从池中获取,使用完毕后放回池中。
- 优化函数参数传递
- 策略:避免传递大对象指针,若对象不需要被修改,传递值可以减少内存逃逸。若对象需要被修改,可以考虑传递指针,但要确保指针的生命周期合理。
- 示例:
package main
import (
"fmt"
)
type BigStruct struct {
Data [10000]int
}
// 优化前,传递指针,可能导致内存逃逸
func badPass(p *BigStruct) {
// 操作p
}
// 优化后,传递值,减少内存逃逸风险(如果对象不需要被修改)
func goodPass(s BigStruct) {
// 操作s
}
- 实施步骤:分析函数对参数的操作,若只是读取数据,传递值而不是指针。
提升垃圾回收性能
- 调整垃圾回收器参数
- 策略:Go 1.14及以后版本,可以通过设置
GODEBUG=gctrace=1
环境变量来查看垃圾回收的详细信息,根据这些信息来调整垃圾回收器的参数。例如,可以调整垃圾回收的触发比例(GOGC
环境变量)。
- 示例:
export GODEBUG=gctrace=1
go run main.go
- 实施步骤:通过观察
gctrace
输出的信息,如垃圾回收的频率、停顿时间等,调整GOGC
的值。如果垃圾回收过于频繁,可以适当增大GOGC
的值(默认100),减少垃圾回收频率,但可能会导致内存占用增加;如果内存占用过高,可以适当减小GOGC
的值。
- 减少长生命周期对象的引用
- 策略:确保长生命周期对象不会持有对短生命周期对象不必要的引用,这样短生命周期对象可以及时被垃圾回收。
- 示例:
package main
import (
"fmt"
)
type Short struct {
// 短生命周期对象的字段
}
type Long struct {
Refs []*Short
}
func main() {
long := Long{}
short := Short{}
long.Refs = append(long.Refs, &short)
// 如果short不再需要被使用,需要及时清理long.Refs中对short的引用
long.Refs = nil
}
- 实施步骤:在代码中查找长生命周期对象持有短生命周期对象引用的地方,在短生命周期对象不再需要被长生命周期对象引用时,及时切断引用。
- 使用并发垃圾回收友好的数据结构
- 策略:选择适合并发垃圾回收的数据结构,如
map
在并发读写时可能导致垃圾回收压力增大,可以考虑使用sync.Map
,它对并发操作和垃圾回收更友好。
- 示例:
package main
import (
"fmt"
"sync"
)
func main() {
var mu sync.Mutex
m := make(map[string]int)
// 并发读写map,需要加锁,且可能影响垃圾回收
go func() {
mu.Lock()
m["key1"] = 1
mu.Unlock()
}()
var syncMap sync.Map
// 使用sync.Map进行并发读写,对垃圾回收更友好
go func() {
syncMap.Store("key2", 2)
}()
}
- 实施步骤:在需要并发操作数据结构的场景中,优先选择对并发垃圾回收友好的数据结构,如
sync.Map
代替普通map
。