GPM调度模型

想要进行性能优化首先要了解最基础的底层模型

  • 一个 G 就是一个 goroutine,在 runtime 中通过类型 g 来表示。当一个 goroutine 退出时,g 对象会
    被放到一个空闲的 g 对象池中以用于后续的 goroutine 的使用(减少内存分配开销)。
  • 一个 M 就是一个系统的线程,系统线程可以执行用户的 go 代码、runtime 代码、系统调用或者空
    闲等待。在 runtime 中通过类型 m 来表示。在同一时间,可能有任意数量的 M,因为任意数量的 M
    可能会阻塞在系统调用中。(当一个 M 执行阻塞的系统调用时,会将 M 和 P 解绑,并创建出一个新
    的 M 来执行 P 上的其它 G。)
  • 最后,一个 P 代表了执行用户 go 代码所需要的资源,比如调度器状态、内存分配器状态等。在
    runtime 中通过类型 p 来表示。P 的数量精确地(exactly)等于 GOMAXPROCS。一个 P 可以被理
    解为是操作系统调度器中的 CPU,p 类型可以被理解为是每个 CPU 的状态。

调度器的工作是将一个 G(需要执行的代码)、一个 M(代码执行的地方)和一个 P(代码执行所需要
的权限和资源)结合起来。当一个 M 停止执行用户代码的时候(比如进入阻塞的系统调用的时候),
就需要把它的 P 归还到空闲的 P 池中;为了继续执行用户的 go 代码(比如从阻塞的系统调用退出
的时候),就需要从空闲的 P 池中获取一个 P。
所有的 g、m 和 p 对象都是分配在堆上且永不释放的,所以它们的内存使用是很稳定的。得益于此
,runtime 可以在调度器实现中避免写屏障(垃圾回收时需要的一种屏障,会带来一些性能开销)。

如何优化

slice 预分配内存优化

golang内存优化

map 预分配内存优化

golang内存优化

使用 strings.Builder 而不是 bytes.Buffer

函数中尽可能使用值而不是指针

● 使用指针会使逃逸分析将变量分配在堆上
● 分配在栈上的对象会随着栈销毁而回收,不会给 gc 带来压力
● 在栈上进行小对象拷贝的性能很好,比分配对象在堆上要好非常多
● 这个规则适用于函数接受者(receiver):指针仅仅应该用来表示“可修改权”,只有当方法会修改对象,并且修改后的值需要在方法外感知到时,才应该使用指针
● 适用于 slice 类型,如非必要,slice 不要包含指针。

map 存值而不是指针

使用 struct{} 优化

● 可以和普通 struct 一样操作
● 不占用空间
● 指向同一个内存地址(runtime.zerobase,编译器特殊优化)

使用 atomic 优化

单纯使用锁是调度系统锁
golang内存优化
使用原子化操作内存更低
golang内存优化

使用不带缓冲区的 channel

golang内存优化

一些工具

● go tool pprof
● go tool trace
● go build -gcflags=”-m”
● GODEBUG=”gctrace=1”
● go get benchmark