go高性能编码规范

2023-03-25  本文已影响0人  mafa1993

go高性能编码规范

总体原则

  1. 高效使用内存,减少内存分配和占用,减少逃逸,内存优化复用,减少gc
  2. 提升并发性,减少锁阻塞,减少访问冲突,协程池复用
  3. 合理使用语言特性,减少系统和cgo调用,减少低效机制使用(如panic、defer等)

数据结构

  1. 大数组和结构体少使用值传递
  2. 从数组或者切片创建不同生命周期的的切片时,考虑拷贝
  3. 字符串多次拼接使用strings.Builder而不是fmt.Sprintf
  4. 减少fmt库的动态解析,减少隐式逃逸
  5. 指针字段建议放在头部使用
  6. map slice channel减少使用指针
  7. 并发访问同一对象的不同属性,使用padding提升cpu cache

语言基础

  1. 无需动态生成错误变量时,建议使用预定义错误常量
  2. for range循环不支持inline,数组和切片强制使用for循环
  3. 1.17之前建议减少闭包
  4. 接口断言后调用方法
  5. 少使用121型断言
  6. 通过反射给结构体字段赋值时,建议使用reflect.Value.Field() 或FieldByIndex()

关键字

  1. 使用defer禁止违反open coded原则
  2. 尽量少使用defer
  3. 超过8字节对象所构建的数组,尽量用下标访问,避免使用for range
  4. 避免使用panic和recover
  5. 符合类型slice和map 使用make初始化时,尽量明确长度

并发

  1. 减少并发相互干扰
  2. 减少系统调用,包含cgo
  3. 提升并发度
    • 禁止使用runtime.LockOSThread
    • 减少所得持有范围
    • 读多写少场景使用读写锁替换互斥锁
    • 使用原子操作替代锁,i++这种属于原子操作
    • 合理使用sync.WaitGroup提高并发性能
    • 明确锁的保护对象,防止锁的范围扩大
  4. 线程安全,使用sync.Once 构建单例
  5. 降低用户资源
    • 避免频繁使用time.After()
    • 禁止高并发调用无系统轮训机制的同步系统调用(基本上处理网络操作都没有轮训机制,如本地文件的io操作)
    • 读取网络或文件数据处理时,避免读取到byte中,尽量使用流式io结构
  6. 最佳配置,避免高频创建、销毁goroutine,建议池化复用,设置合理的GOMAXPROCS

内存管理

  1. 临时对象合并存储,从而减少指针数量
  2. 当系统存在大量临时对象的申请和释放场景,建议使用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)
        }
    }
}
  1. 避免使用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) 
}

  1. 海量数据避免使用链表存储,建议使用指定长度的slice替代
  2. 设置合理的gogc

跨语言调用

  1. 避免使用cgo或减少跨cgo调用,go调用c的时候,栈空间会发生切换,并且不需要将p和m解绑
  2. 控制cgo调用goroutine数量

标准库

  1. 作为http客户端时,为每个http client创建超时时间
  2. 作为client时,如果服务调用场景确定,使用自定义transport复用链接池
  3. 使用easyjson替换json.marshal、unmarsha1
  4. 高并发场景使用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/")
}   
上一篇 下一篇

猜你喜欢

热点阅读