go高性能编码规范
总体原则
- 高效使用内存,减少内存分配和占用,减少逃逸,内存优化复用,减少gc
- 提升并发性,减少锁阻塞,减少访问冲突,协程池复用
- 合理使用语言特性,减少系统和cgo调用,减少低效机制使用(如panic、defer等)
数据结构
- 大数组和结构体少使用值传递
- 从数组或者切片创建不同生命周期的的切片时,考虑拷贝
- 字符串多次拼接使用strings.Builder而不是fmt.Sprintf
- 减少fmt库的动态解析,减少隐式逃逸
- 指针字段建议放在头部使用
- map slice channel减少使用指针
- 并发访问同一对象的不同属性,使用padding提升cpu cache
语言基础
- 无需动态生成错误变量时,建议使用预定义错误常量
- for range循环不支持inline,数组和切片强制使用for循环
- 1.17之前建议减少闭包
- 接口断言后调用方法
- 少使用121型断言
- 通过反射给结构体字段赋值时,建议使用reflect.Value.Field() 或FieldByIndex()
关键字
- 使用defer禁止违反open coded原则
- 尽量少使用defer
- 超过8字节对象所构建的数组,尽量用下标访问,避免使用for range
- 避免使用panic和recover
- 符合类型slice和map 使用make初始化时,尽量明确长度
并发
- 减少并发相互干扰
- 减少系统调用,包含cgo
- 提升并发度
- 禁止使用runtime.LockOSThread
- 减少所得持有范围
- 读多写少场景使用读写锁替换互斥锁
- 使用原子操作替代锁,i++这种属于原子操作
- 合理使用sync.WaitGroup提高并发性能
- 明确锁的保护对象,防止锁的范围扩大
- 线程安全,使用sync.Once 构建单例
- 降低用户资源
- 避免频繁使用time.After()
- 禁止高并发调用无系统轮训机制的同步系统调用(基本上处理网络操作都没有轮训机制,如本地文件的io操作)
- 读取网络或文件数据处理时,避免读取到byte中,尽量使用流式io结构
- 最佳配置,避免高频创建、销毁goroutine,建议池化复用,设置合理的GOMAXPROCS
内存管理
- 临时对象合并存储,从而减少指针数量
- 当系统存在大量临时对象的申请和释放场景,建议使用sync.Pool等支持对象复用的机制来避免对象的反复申请,减少gc压力
// sync.pool
package main
import "sync"
type Person struct {
Name string
}
//Initialing pool
var personalPool = sync.Pool{
// New optionally specifies a function to generate
// a value when Get would otherwise return nil.
New: func() interface{} {
return &Person{}
},
}
func main() {
// Get hold of an instance
newPerson := personalPool.Get().(*Person)
// Defer release function
// After that the same instance is
// reusable by another routine
defer personalPool.Put(newPerson)
// using instance
newPerson.Name = "Jack"
}
// 压测
package main
import (
"sync"
"testing"
)
type Person struct {
Age int
}
var (
personPool = sync.Pool{
New: func() interface{} {
return &Person{}
},
}
)
func BenchmarkWithoutPool(b *testing.B) {
var p *Person
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
for j := 0; j < 10000; j++ {
p = new(Person)
p.Age = 23
}
}
}
func BenchmarkWithPool(b *testing.B) {
var p *Person
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
for j := 0; j < 10000; j++ {
p = personPool.Get().(*Person)
p.Age = 23
personPool.Put(p)
}
}
}
- 避免使用sync.Map引起多倍的内存对象数,增加gc压力
//sync map适用于读多写少场景
package main
import (
"fmt"
"sync"
)
func main() {
var m sync.Map
// 1. 写入
m.Store("test", 18)
m.Store("mo", 20)
// 2. 读取
age, _ := m.Load("test")
fmt.Println(age.(int))
// 3. 遍历
m.Range(func(key, value interface{}) bool {
name := key.(string)
age := value.(int)
fmt.Println(name, age)
return true
})
// 4. 删除
m.Delete("test")
age, ok := m.Load("test")
fmt.Println(age, ok)
// 5. 读取或写入
m.LoadOrStore("mo", 100)
age, _ = m.Load("mo")
fmt.Println(age)
}
- 海量数据避免使用链表存储,建议使用指定长度的slice替代
- 设置合理的gogc
跨语言调用
- 避免使用cgo或减少跨cgo调用,go调用c的时候,栈空间会发生切换,并且不需要将p和m解绑
- 控制cgo调用goroutine数量
标准库
- 作为http客户端时,为每个http client创建超时时间
- 作为client时,如果服务调用场景确定,使用自定义transport复用链接池
- 使用easyjson替换json.marshal、unmarsha1
- 高并发场景使用fasthttp替换net/http
var (
t = &http.Transport {
//...
MaxIdleConns: 100, //总连接池的连接数
MaxIdleConnsPerHost: 2, //每个host的最大空闲连接数
MaxConnsPerHost: 5, //每个host的最大连接数
IdleConnTimeout: 90 * time.Second, //空闲连接回收关闭超时时间
//...
}
)
func foo()
{
httpClient := &http.Client {
Transport: t,
//...
}
httpClient.Get("http://xx.xx.xx.xx/api/")
}
网友评论