Golang学习笔记:后端性能优化秘籍(持续更新)
减少堆对象分配
1.小对象使用结构体而非指针
func createUser() *User {return &User{ID: 1, Name: "Alice"} // 逃逸到堆
}
改成下面的写法编成栈分配,生命周期结束自动回收
func createUser() User {return User{ID: 1, Name: "Alice"} // 栈分配,函数结束后自动回收
}
2.列表对象使用值切片而非指针切片
场景
type User struct {ID      int64Name    stringValue   float64Status  int// 假设总共约 50-100 字节
}// 场景:返回 1000 个 User 对象
值切片内存布局
[ User1 ][ User2 ][ User3 ]... // 连续内存块
一次堆分配,GC只追踪一个对象
指针切片布局
[ ptr1 ][ ptr2 ][ ptr3 ]... → 各自指向堆上的 User 对象
1001次堆分配(切片数组+1000个User对象),内存碎片化,GC需追踪1001个对象
3.参数传递代替闭包捕获
func main() {x := 42go func() {fmt.Println(x) // x 逃逸到堆}()
}
func main() {x := 42go func(val int) {fmt.Println(val) // val 通过值传递,保留在栈上}(x)
}
使用对象池
var pool = sync.Pool{New: func() interface{} { return make([]byte, 1024) },
}
func processRequest() {buf := pool.Get().([]byte) // 从池中获取(可能复用)defer pool.Put(buf)        // 放回池中// 使用 buf...
}
预分配内存
// 未优化:多次扩容
var data []int
for i := 0; i < 1000; i++ {data = append(data, i)  // 可能触发多次堆分配
}
预分配内存,可以避免中途多次触发GC扫描,同时减少数据迁移的开销。
// 优化:单次预分配
data := make([]int, 0, 1000)  // 一次性分配底层数组
for i := 0; i < 1000; i++ {data = append(data, i)    
}
非关键路径异步化处理
对于耗时高的非关键操作,采用异步化操作,避免长时间阻塞主流程。
避免反射
反射需要在运行时动态检查数据类型和创建临时对象(每次reflect.ValueOf()或reflect.TypeOf()调用至少产生1次堆分配)。引入泛型,泛型在编译时期会生成对应类型的代码,运行时无需校验类型和分配临时对象。
 性能上:泛型>强制类型转换/断言>反射
字符串拼接
- strings.Builder性能最高,底层是[]byte,可动态扩容;
- strings.Join底层是strings.Builder,一次性计算分配内存,性能差不多;
- +运算符,需要不断创建新的临时对象;
- fmt.Sprintf(),性能很差,涉及到反射。性能敏感场景避免使用;
工具排查
使用pprof,推荐参考
切片清空
清空切片(如使用 buf[:0] 或重新初始化)是 Go 后端开发中常见的性能优化手段,尤其在高频操作或处理大量数据的场景中,合理清空切片能有效减少内存分配和垃圾回收(GC)压力,提升程序性能。
循环中复用切片(高频操作)
在循环(尤其是高迭代次数的循环)中处理数据时,若每次迭代都需要一个临时切片存储中间结果,复用切片比每次创建新切片更高效。
func processBatch(data [][]byte) {// 预分配一个切片,容量足够容纳单次处理的最大数据量buffer := make([]byte, 0, 1024) for _, item := range data {// 清空切片内容,复用底层数组buffer = buffer[:0]// 填充新数据(如解析 item 到 buffer)buffer = append(buffer, item...)buffer = append(buffer, '\n')// 处理 buffer...}
}
池化资源
在高并发场景中,可结合 sync.Pool 实现切片的池化管理,避免重复创建和销毁切片,进一步提升性能。
var slicePool = sync.Pool{New: func() interface{} {// 初始化一个具有一定容量的切片return make([]int, 0, 100)},
}// 从池获取切片,使用后清空并放回
func getSlice() []int {return slicePool.Get().([]int)
}func putSlice(s []int) {// 清空切片内容,保留容量后放回池s = s[:0]slicePool.Put(s)
}// 高并发场景中使用
func handleRequest() {s := getSlice()defer putSlice(s)// 使用 s 处理请求...s = append(s, 1, 2, 3)
}
