常规手段

1.sync.Pool

sync.PoolfasthttpRequestCtxsync.PoolgoroutinefasthttpRequestCtxfasthttp
go逃逸检查

2.string2bytes & bytes2string

这也是两个比较常规的优化手段,核心还是复用对象,减少内存分配。

在go标准库中也有类似的用法gostringnocopy

string2bytes
unsafe.Pointerrecover

3.协程池

绝大部分应用场景,go是不需要协程池的。当然,协程池还是有一些自己的优势:

goroutinegoroutine
goroutine

4.反射

go里面的反射代码可读性本来就差,常见的优化手段进一步牺牲可读性。
而且后续马上就有范型的支持,所以若非必要,建议不要优化反射部分的代码

比较常见的优化手段有:

unsafe.Pointerstructinterface->[]byte

5.减小锁消耗

并发场景下,对临界区加锁比较常见。带来的性能隐患也必须重视。常见的优化手段有:

另类手段

1. golink

golink在官方的文档里有介绍,使用格式:

//go:linkname FastRand runtime.fastrand
func FastRand() uint32
FastRandruntime.fastrand
runtimemathgoroutineruntime
Benchmark_MathRand-12       84419976            13.98 ns/op
Benchmark_Runtime-12        505765551           2.158 ns/op
time.Now()runtime.walltime1runtime.nanotimeruntime.walltime1
Benchmark_Time-12       16323418            73.30 ns/op
Benchmark_Runtime-12    29912856            38.10 ns/op
runtime.nanotimetime.Now
//go:linkname nanotime1 runtime.nanotime1
func nanotime1() int64
func main() {
    defer func( begin int64) {
        cost := (nanotime1() - begin)/1000/1000
        fmt.Printf("cost = %dms \n" ,cost)
    }(nanotime1())
    
    time.Sleep(time.Second)
}

运行结果:cost = 1000ms 

2. log-函数名称行号的获取

虽然很多高性能的日志库,默认都不开启记录行号。但实际业务场景中,我们还是觉得能打印最好。

在runtime中,函数行号和函数名称的获取分为两步:

runtimegoroutinefuncInfo
runtimepc
var(
    m sync.Map
)
func Caller(skip int)(pc uintptr, file string, line int, ok bool){
    rpc := [1]uintptr{}
    n := runtime.Callers(skip+1, rpc[:])
    if n < 1 {
        return
    }
    var (
        frame  runtime.Frame
        )
    pc  = rpc[0]
    if item,ok:=m.Load(pc);ok{
        frame = item.(runtime.Frame)
    }else{
        tmprpc := []uintptr{
            pc,
        }
        frame, _ = runtime.CallersFrames(tmprpc).Next()
        m.Store(pc,frame)
    }
    return frame.PC,frame.File,frame.Line,frame.PC!=0

6.simd

simdsimd
llvmcgocgocgo
llvm
csimdllvm.s.s

以下开源库用到了simd,可以参考:

simd
  1. 难以维护,要么需要懂汇编的大神,要么需要引入第三方语言
  2. 跨平台支持不够,需要对不同平台汇编指令做适配
  3. 汇编代码很难调试,作为使用方来讲,完全黑盒

7.jit

go中使用jit的方式可以参考Writing a JIT compiler in Golang

json

这种使用方式个人感觉在go中意义不大,仅供参考

总结

过早的优化是万恶之源,千万不要为了优化而优化

  1. pprof分析,竞态分析,逃逸分析,这些基础的手段是必须要学会的
  2. 常规的优化技巧是比较实用的,他们往往能解决大部分的性能问题并且足够安全。
  3. 在一些着重性能的基础库中,使用一些非常规的优化手段也是可以的,但必须要权衡利弊,不要过早放弃可读性,兼容性和稳定性。